From 1e268ecbf1076dbba9a85c62d3baebc085df3363 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 11 Jun 2025 12:16:19 -0400 Subject: [PATCH 001/141] Exposed UI from outside container --- examples/agent_ui_examples.py | 6 +++++- examples/computer_ui_examples.py | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/examples/agent_ui_examples.py b/examples/agent_ui_examples.py index 17130f5c..d5a37119 100644 --- a/examples/agent_ui_examples.py +++ b/examples/agent_ui_examples.py @@ -18,4 +18,8 @@ from agent.ui.gradio.app import create_gradio_ui if __name__ == "__main__": print("Launching Computer-Use Agent Gradio UI with advanced features...") app = create_gradio_ui() - app.launch(share=False) + app.launch( + share=False, + server_name="0.0.0.0", + server_port=7860, + ) diff --git a/examples/computer_ui_examples.py b/examples/computer_ui_examples.py index 4116b41e..0c1d0974 100644 --- a/examples/computer_ui_examples.py +++ b/examples/computer_ui_examples.py @@ -18,7 +18,11 @@ from computer.ui.gradio.app import create_gradio_ui if __name__ == "__main__": print("Launching Computer Interface Gradio UI with advanced features...") app = create_gradio_ui() - app.launch(share=False) + app.launch( + share=False, + server_name="0.0.0.0", + server_port=7860, + ) # Optional: Using the saved dataset # import datasets From cd7147df009018f4856d033dd63c30272e647ec2 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 11 Jun 2025 12:16:28 -0400 Subject: [PATCH 002/141] Fixed incorrect .sh line endings on windows --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..3852dc32 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +* text=auto +*.sh text eol=lf \ No newline at end of file From 8eca52410a0ac7f3001a23f26850aba7fbf7ea62 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 11 Jun 2025 12:16:50 -0400 Subject: [PATCH 003/141] Added platform detection, opened port 7860 --- scripts/run-docker-dev.sh | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/scripts/run-docker-dev.sh b/scripts/run-docker-dev.sh index 8f96b355..d1301d09 100755 --- a/scripts/run-docker-dev.sh +++ b/scripts/run-docker-dev.sh @@ -24,6 +24,24 @@ IMAGE_NAME="cua-dev-image" CONTAINER_NAME="cua-dev-container" PLATFORM="linux/arm64" +# Detect platform based on architecture +arch=$(uname -m) + +if [[ $arch == x86_64* ]]; then + PLATFORM="linux/amd64" + print_info "X64 Architecture detected, using platform: ${PLATFORM}" +elif [[ $arch == i*86 ]]; then + PLATFORM="linux/386" + print_info "X32 Architecture detected, using platform: ${PLATFORM}" +elif [[ $arch == arm* ]] || [[ $arch == aarch64 ]]; then + PLATFORM="linux/arm64" + print_info "ARM Architecture detected, using platform: ${PLATFORM}" +else + # Fallback to amd64 for unknown architectures + PLATFORM="linux/amd64" + print_info "Unknown architecture ($arch), defaulting to platform: ${PLATFORM}" +fi + # Environment variables PYTHONPATH="/app/libs/core:/app/libs/computer:/app/libs/agent:/app/libs/som:/app/libs/pylume:/app/libs/computer-server" @@ -56,6 +74,7 @@ case "$1" in -e PYTHONPATH=${PYTHONPATH} \ -e DISPLAY=${DISPLAY:-:0} \ -e PYLUME_HOST="host.docker.internal" \ + -p 7860:7860 \ ${IMAGE_NAME} bash else # Run the specified example @@ -73,6 +92,7 @@ case "$1" in -e PYTHONPATH=${PYTHONPATH} \ -e DISPLAY=${DISPLAY:-:0} \ -e PYLUME_HOST="host.docker.internal" \ + -p 7860:7860 \ ${IMAGE_NAME} python "/app/examples/$2" fi ;; From c5701c95f3fb539f96324a6b29cb2549edd63981 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 11 Jun 2025 13:03:34 -0400 Subject: [PATCH 004/141] Initial dev container --- .devcontainers/README.md | 60 ++++++++++++++++++++++++++++++++ .devcontainers/devcontainer.json | 54 ++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 .devcontainers/README.md create mode 100644 .devcontainers/devcontainer.json diff --git a/.devcontainers/README.md b/.devcontainers/README.md new file mode 100644 index 00000000..be1b6512 --- /dev/null +++ b/.devcontainers/README.md @@ -0,0 +1,60 @@ +# Dev Container Setup + +This repository includes a Dev Container configuration that simplifies the development setup to just 3 steps: + +## Quick Start + +1. **Install Dev Containers extension** in VS Code +2. **Clone and open in container**: + - Press `Ctrl+Shift+P` (or `Cmd+Shift+P` on Mac) + - Type "Dev Containers: Clone Repository in Container Volume" + - Paste the repository URL: `https://github.com/trycua/cua.git` +3. **Hit play**: Once the container builds, you're ready to develop! + +## What's Included + +The dev container automatically: + +- ✅ Sets up Python 3.11 environment +- ✅ Installs all system dependencies (build tools, OpenGL, etc.) +- ✅ Configures Python paths for all packages +- ✅ Installs Python extensions (Black, Ruff, Pylance) +- ✅ Forwards port 7860 for the Gradio web UI +- ✅ Mounts your source code for live editing +- ✅ Creates the required `.env.local` file + +## Running Examples + +After the container is built, you can run examples directly: + +```bash +# Run the agent UI (Gradio web interface) +python examples/agent_ui_examples.py + +# Run computer examples +python examples/computer_examples.py + +# Run computer UI examples +python examples/computer_ui_examples.py +``` + +The Gradio UI will be available at `http://localhost:7860` and will automatically forward to your host machine. + +## Environment Variables + +You'll need to add your API keys to `.env.local`: + +```bash +# Required for Anthropic provider +ANTHROPIC_API_KEY=your_anthropic_key_here + +# Required for OpenAI provider +OPENAI_API_KEY=your_openai_key_here +``` + +## Notes + +- The container connects to `host.docker.internal:7777` for Lume server communication +- All Python packages are pre-installed and configured +- Source code changes are reflected immediately (no rebuild needed) +- The container uses the same Dockerfile as the regular Docker development environment diff --git a/.devcontainers/devcontainer.json b/.devcontainers/devcontainer.json new file mode 100644 index 00000000..89cb2754 --- /dev/null +++ b/.devcontainers/devcontainer.json @@ -0,0 +1,54 @@ +{ + "name": "C/ua - OSS", + "build": { + "dockerfile": "../Dockerfile" + }, + "containerEnv": { + "DISPLAY": "", + "PYTHONPATH": "/app/libs/core:/app/libs/computer:/app/libs/agent:/app/libs/som:/app/libs/pylume:/app/libs/computer-server:/app/libs/mcp-server", + "PYLUME_HOST": "host.docker.internal" + }, + "forwardPorts": [7860], + "portsAttributes": { + "7860": { + "label": "C/ua web client (Gradio)", + "onAutoForward": "silent" + } + }, + "postCreateCommand": "echo 'PYTHON_BIN=python' > /app/.env.local", + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python", + "ms-python.black-formatter", + "charliermarsh.ruff", + "ms-python.vscode-pylance" + ], + "settings": { + "python.defaultInterpreterPath": "/usr/local/bin/python", + "python.terminal.activateEnvironment": false, + "[python]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "ms-python.black-formatter", + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + } + }, + "python.linting.enabled": true, + "python.linting.ruffEnabled": true, + "python.formatting.provider": "black", + "files.watcherExclude": { + "**/.venv/**": true, + "**/node_modules/**": true, + "**/__pycache__/**": true, + "**/.pytest_cache/**": true + } + } + } + }, + "mounts": [ + "source=${localWorkspaceFolder},target=/app,type=bind,consistency=cached" + ], + "workspaceFolder": "/app", + "shutdownAction": "stopContainer" +} From 337167fcf3a73d74361a2e502368b00a11ee0ada Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 11 Jun 2025 13:10:52 -0400 Subject: [PATCH 005/141] rename devcontainer folder --- {.devcontainers => .devcontainer}/README.md | 2 +- {.devcontainers => .devcontainer}/devcontainer.json | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename {.devcontainers => .devcontainer}/README.md (96%) rename {.devcontainers => .devcontainer}/devcontainer.json (100%) diff --git a/.devcontainers/README.md b/.devcontainer/README.md similarity index 96% rename from .devcontainers/README.md rename to .devcontainer/README.md index be1b6512..558cbec7 100644 --- a/.devcontainers/README.md +++ b/.devcontainer/README.md @@ -7,7 +7,7 @@ This repository includes a Dev Container configuration that simplifies the devel 1. **Install Dev Containers extension** in VS Code 2. **Clone and open in container**: - Press `Ctrl+Shift+P` (or `Cmd+Shift+P` on Mac) - - Type "Dev Containers: Clone Repository in Container Volume" + - Type "Dev Containers: Clone Repository in Container Volume..." - Paste the repository URL: `https://github.com/trycua/cua.git` 3. **Hit play**: Once the container builds, you're ready to develop! diff --git a/.devcontainers/devcontainer.json b/.devcontainer/devcontainer.json similarity index 100% rename from .devcontainers/devcontainer.json rename to .devcontainer/devcontainer.json From 6a50d44c90bfb68a8d4079384afa3fc81e4fcd93 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 11 Jun 2025 13:26:25 -0400 Subject: [PATCH 006/141] fixes --- .devcontainer/devcontainer.json | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 89cb2754..b5b39fe0 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -46,9 +46,6 @@ } } }, - "mounts": [ - "source=${localWorkspaceFolder},target=/app,type=bind,consistency=cached" - ], - "workspaceFolder": "/app", - "shutdownAction": "stopContainer" + "workspaceMount": "type=bind,source=${localWorkspaceFolder},target=/app", + "workspaceFolder": "/app" } From f8045d1bc8c7466fc0c0eea6d7c0ea51347871db Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 11 Jun 2025 13:30:42 -0400 Subject: [PATCH 007/141] Removed unused vars --- .devcontainer/devcontainer.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b5b39fe0..a0302389 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -45,7 +45,5 @@ } } } - }, - "workspaceMount": "type=bind,source=${localWorkspaceFolder},target=/app", - "workspaceFolder": "/app" + } } From bfb71443bfde1a30a55fe52f3fe9e7f1c614bcd5 Mon Sep 17 00:00:00 2001 From: ddupont <3820588+ddupont808@users.noreply.github.com> Date: Wed, 11 Jun 2025 13:48:08 -0400 Subject: [PATCH 008/141] Update README.md --- .devcontainer/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.devcontainer/README.md b/.devcontainer/README.md index 558cbec7..e7cee264 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -4,6 +4,8 @@ This repository includes a Dev Container configuration that simplifies the devel ## Quick Start +![Clipboard-20250611-174726-573](https://github.com/user-attachments/assets/603e2b25-5763-41cd-a4bc-01fe5d57482b) + 1. **Install Dev Containers extension** in VS Code 2. **Clone and open in container**: - Press `Ctrl+Shift+P` (or `Cmd+Shift+P` on Mac) From 2050ea84ed1f9651f2cc2da346c77cf0530b2a1b Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 11 Jun 2025 14:03:00 -0400 Subject: [PATCH 009/141] Updated launch config --- .vscode/launch.json | 39 ++++++++----- .vscode/py.code-workspace | 113 -------------------------------------- 2 files changed, 26 insertions(+), 126 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 3ea4279e..eb7f1801 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,5 +1,31 @@ { "configurations": [ + { + "name": "Agent UI", + "type": "debugpy", + "request": "launch", + "program": "examples/agent_ui_examples.py", + "console": "integratedTerminal", + "justMyCode": false, + "python": "${workspaceFolder:cua-root}/.venv/bin/python", + "cwd": "${workspaceFolder:cua-root}", + "env": { + "PYTHONPATH": "${workspaceFolder:cua-root}/libs/core:${workspaceFolder:cua-root}/libs/computer:${workspaceFolder:cua-root}/libs/agent:${workspaceFolder:cua-root}/libs/som:${workspaceFolder:cua-root}/libs/pylume" + } + }, + { + "name": "Computer UI", + "type": "debugpy", + "request": "launch", + "program": "examples/computer_ui_examples.py", + "console": "integratedTerminal", + "justMyCode": false, + "python": "${workspaceFolder:cua-root}/.venv/bin/python", + "cwd": "${workspaceFolder:cua-root}", + "env": { + "PYTHONPATH": "${workspaceFolder:cua-root}/libs/core:${workspaceFolder:cua-root}/libs/computer:${workspaceFolder:cua-root}/libs/agent:${workspaceFolder:cua-root}/libs/som:${workspaceFolder:cua-root}/libs/pylume" + } + }, { "name": "Run Computer Examples", "type": "debugpy", @@ -26,19 +52,6 @@ "PYTHONPATH": "${workspaceFolder:cua-root}/libs/core:${workspaceFolder:cua-root}/libs/computer:${workspaceFolder:cua-root}/libs/agent:${workspaceFolder:cua-root}/libs/som:${workspaceFolder:cua-root}/libs/pylume" } }, - { - "name": "Run Agent UI Examples", - "type": "debugpy", - "request": "launch", - "program": "examples/agent_ui_examples.py", - "console": "integratedTerminal", - "justMyCode": false, - "python": "${workspaceFolder:cua-root}/.venv/bin/python", - "cwd": "${workspaceFolder:cua-root}", - "env": { - "PYTHONPATH": "${workspaceFolder:cua-root}/libs/core:${workspaceFolder:cua-root}/libs/computer:${workspaceFolder:cua-root}/libs/agent:${workspaceFolder:cua-root}/libs/som:${workspaceFolder:cua-root}/libs/pylume" - } - }, { "name": "Run PyLume Examples", "type": "debugpy", diff --git a/.vscode/py.code-workspace b/.vscode/py.code-workspace index eb853f5a..11751d53 100644 --- a/.vscode/py.code-workspace +++ b/.vscode/py.code-workspace @@ -148,119 +148,6 @@ } ] }, - "launch": { - "version": "0.2.0", - "configurations": [ - { - "name": "Run Computer Examples", - "type": "debugpy", - "request": "launch", - "program": "examples/computer_examples.py", - "console": "integratedTerminal", - "justMyCode": true, - "python": "${workspaceFolder:cua-root}/.venv/bin/python", - "cwd": "${workspaceFolder:cua-root}", - "env": { - "PYTHONPATH": "${workspaceFolder:cua-root}/libs/core:${workspaceFolder:cua-root}/libs/computer:${workspaceFolder:cua-root}/libs/agent:${workspaceFolder:cua-root}/libs/som:${workspaceFolder:cua-root}/libs/pylume" - } - }, - { - "name": "Run Agent Examples", - "type": "debugpy", - "request": "launch", - "program": "examples/agent_examples.py", - "console": "integratedTerminal", - "justMyCode": false, - "python": "${workspaceFolder:cua-root}/.venv/bin/python", - "cwd": "${workspaceFolder:cua-root}", - "env": { - "PYTHONPATH": "${workspaceFolder:cua-root}/libs/core:${workspaceFolder:cua-root}/libs/computer:${workspaceFolder:cua-root}/libs/agent:${workspaceFolder:cua-root}/libs/som:${workspaceFolder:cua-root}/libs/pylume" - } - }, - { - "name": "Run PyLume Examples", - "type": "debugpy", - "request": "launch", - "program": "examples/pylume_examples.py", - "console": "integratedTerminal", - "justMyCode": true, - "python": "${workspaceFolder:cua-root}/.venv/bin/python", - "cwd": "${workspaceFolder:cua-root}", - "env": { - "PYTHONPATH": "${workspaceFolder:cua-root}/libs/core:${workspaceFolder:cua-root}/libs/computer:${workspaceFolder:cua-root}/libs/agent:${workspaceFolder:cua-root}/libs/som:${workspaceFolder:cua-root}/libs/pylume" - } - }, - { - "name": "SOM: Run Experiments (No OCR)", - "type": "debugpy", - "request": "launch", - "program": "examples/som_examples.py", - "args": [ - "examples/test_data", - "--output-dir", "examples/output", - "--ocr", "none", - "--mode", "experiment" - ], - "console": "integratedTerminal", - "justMyCode": false, - "python": "${workspaceFolder:cua-root}/.venv/bin/python", - "cwd": "${workspaceFolder:cua-root}", - "env": { - "PYTHONPATH": "${workspaceFolder:cua-root}/libs/core:${workspaceFolder:cua-root}/libs/computer:${workspaceFolder:cua-root}/libs/agent:${workspaceFolder:cua-root}/libs/som:${workspaceFolder:cua-root}/libs/pylume" - } - }, - { - "name": "SOM: Run Experiments (EasyOCR)", - "type": "debugpy", - "request": "launch", - "program": "examples/som_examples.py", - "args": [ - "examples/test_data", - "--output-dir", "examples/output", - "--ocr", "easyocr", - "--mode", "experiment" - ], - "console": "integratedTerminal", - "justMyCode": false, - "python": "${workspaceFolder:cua-root}/.venv/bin/python", - "cwd": "${workspaceFolder:cua-root}", - "env": { - "PYTHONPATH": "${workspaceFolder:cua-root}/libs/core:${workspaceFolder:cua-root}/libs/computer:${workspaceFolder:cua-root}/libs/agent:${workspaceFolder:cua-root}/libs/som:${workspaceFolder:cua-root}/libs/pylume" - } - }, - { - "name": "Run Computer Server", - "type": "debugpy", - "request": "launch", - "program": "${workspaceFolder}/libs/computer-server/run_server.py", - "console": "integratedTerminal", - "justMyCode": true, - "python": "${workspaceFolder:cua-root}/.venv/bin/python", - "cwd": "${workspaceFolder:cua-root}", - "env": { - "PYTHONPATH": "${workspaceFolder:cua-root}/libs/core:${workspaceFolder:cua-root}/libs/computer:${workspaceFolder:cua-root}/libs/agent:${workspaceFolder:cua-root}/libs/som:${workspaceFolder:cua-root}/libs/pylume" - } - }, - { - "name": "Run Computer Server with Args", - "type": "debugpy", - "request": "launch", - "program": "${workspaceFolder}/libs/computer-server/run_server.py", - "args": [ - "--host", "0.0.0.0", - "--port", "8000", - "--log-level", "debug" - ], - "console": "integratedTerminal", - "justMyCode": false, - "python": "${workspaceFolder:cua-root}/.venv/bin/python", - "cwd": "${workspaceFolder:cua-root}", - "env": { - "PYTHONPATH": "${workspaceFolder:cua-root}/libs/core:${workspaceFolder:cua-root}/libs/computer-server" - } - } - ] - }, "compounds": [ { "name": "Run Computer Examples + Server", From 47a36fa5570cc2255274472c5f283d5c44fc09c8 Mon Sep 17 00:00:00 2001 From: ddupont <3820588+ddupont808@users.noreply.github.com> Date: Wed, 11 Jun 2025 14:08:43 -0400 Subject: [PATCH 010/141] Update README.md --- .devcontainer/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/README.md b/.devcontainer/README.md index e7cee264..ed66c86e 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -4,7 +4,7 @@ This repository includes a Dev Container configuration that simplifies the devel ## Quick Start -![Clipboard-20250611-174726-573](https://github.com/user-attachments/assets/603e2b25-5763-41cd-a4bc-01fe5d57482b) +![Clipboard-20250611-180809-459](https://github.com/user-attachments/assets/447eaeeb-0eec-4354-9a82-44446e202e06) 1. **Install Dev Containers extension** in VS Code 2. **Clone and open in container**: From 38323bdad7edb263199124119e32657b44879bfa Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 11 Jun 2025 14:18:35 -0400 Subject: [PATCH 011/141] Set default workspace --- .devcontainer/devcontainer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a0302389..dd17d69b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -15,7 +15,6 @@ "onAutoForward": "silent" } }, - "postCreateCommand": "echo 'PYTHON_BIN=python' > /app/.env.local", "customizations": { "vscode": { "extensions": [ @@ -45,5 +44,6 @@ } } } - } + }, + "workspaceFolder": "/.vscode/py.code-workspace" } From e1a06b1faea6ca56c7592b2a495bb2c7efa217ac Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 11 Jun 2025 14:22:26 -0400 Subject: [PATCH 012/141] updated workspace folder --- .devcontainer/devcontainer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index dd17d69b..4cea2941 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -45,5 +45,5 @@ } } }, - "workspaceFolder": "/.vscode/py.code-workspace" + "workspaceFolder": "/app/.vscode/py.code-workspace" } From bd9c2df944feb50ef46e876d5b68c65bd70bb7ff Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 11 Jun 2025 14:23:03 -0400 Subject: [PATCH 013/141] Fixed path --- .devcontainer/devcontainer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4cea2941..918ac9ab 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -45,5 +45,5 @@ } } }, - "workspaceFolder": "/app/.vscode/py.code-workspace" + "workspaceFolder": "/workspaces/cua/.vscode/py.code-workspace" } From 4673144026ae4ac0324ebac154706a487cf77edb Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 11 Jun 2025 14:28:35 -0400 Subject: [PATCH 014/141] workspace setup --- .devcontainer/devcontainer.json | 3 +- .devcontainer/post-install.sh | 73 +++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 .devcontainer/post-install.sh diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 918ac9ab..b3e80233 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -45,5 +45,6 @@ } } }, - "workspaceFolder": "/workspaces/cua/.vscode/py.code-workspace" + // Automatically run post-install.sh after container is created + "postCreateCommand": "/bin/bash .devcontainer/post-install.sh" } diff --git a/.devcontainer/post-install.sh b/.devcontainer/post-install.sh new file mode 100644 index 00000000..ba16a25c --- /dev/null +++ b/.devcontainer/post-install.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash + +if [ "$1" == "--help" ]; then + + cat << EOF +Run from project folder, auto opens vscode in some mode depending on folder contents: + +* Folder contains .devcontainer/devcontainer.json and .code-workspace file: vscode opens in devcontainer, workspace file is loaded +* Folder contains .devcontainer/devcontainer.json: vscode opens in devcontainer +* Folder contains .code-workspace file: Workspace is opened in vscode +* Folder contains no .code-workspace and no devcontainer: vscode is opened, loading contents of the current folder + +This script was created for WSL2, probably works the same way for native Linux, but untested + +Assumes the following filestructure: + + +| +| -- .code-workspace +| -- ./devcontainer/devcontainer.json +| -- ... + +Note: If you set workspaceFolder or workspaceMount in devcontainer.json this may cause issues + Also, if .devcontainer/devcontainer.json is not in the root of your repository, you may get in trouble + refer to https://code.visualstudio.com/remote/advancedcontainers/change-default-source-mount + +EOF + exit 0 +fi + +# check for dependencies +if ! command -v xxd &> /dev/null; then + echo "xxd command not found, install with" + echo "sudo apt install xxd" + exit 1 +fi + +DEVCONTAINER_JSON="$PWD/.devcontainer/devcontainer.json" +CODE_WS_FILE=$(ls $PWD/*.code-workspace 2>/dev/null) + +if [ ! -f "$DEVCONTAINER_JSON" ];then + + # open code without container + + if [ -f "$CODE_WS_FILE" ]; then + echo "Opening vscode workspace from $CODE_WS_FILE" + code $CODE_WS_FILE + else + echo "Opening vscode in current directory" + code . + fi + exit 0 +fi + +# open devcontainer +HOST_PATH=$(echo $(wslpath -w $PWD) | sed -e 's,\\,\\\\,g') +WORKSPACE="/workspaces/$(basename $PWD)" + +URI_SUFFIX= +if [ -f "$CODE_WS_FILE" ]; then + # open workspace file + URI_TYPE="--file-uri" + URI_SUFFIX="$WORKSPACE/$(basename $CODE_WS_FILE)" + echo "Opening vscode workspace file within devcontainer" +else + URI_TYPE="--folder-uri" + URI_SUFFIX="$WORKSPACE" + echo "Opening vscode within devcontainer" +fi + +URI="{\"hostPath\":\"$HOST_PATH\",\"configFile\":{\"\$mid\":1,\"path\":\"$DEVCONTAINER_JSON\",\"scheme\":\"vscode-fileHost\"}}" +URI_HEX=$(echo "${URI}" | xxd -c 0 -p) +code ${URI_TYPE}="vscode-remote://dev-container%2B${URI_HEX}${URI_SUFFIX}" & \ No newline at end of file From e9b427fe1be2642d6aeaae5a8b888adc9373f2bb Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 11 Jun 2025 14:31:43 -0400 Subject: [PATCH 015/141] Add xxd --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 4bd8c6e9..ac9531af 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,6 +21,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ iputils-ping \ net-tools \ sed \ + xxd \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* From 4be4f00f468aa3c0ee7eb7a493aff75e186d9597 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 11 Jun 2025 14:41:17 -0400 Subject: [PATCH 016/141] updated for mac --- .devcontainer/post-install.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.devcontainer/post-install.sh b/.devcontainer/post-install.sh index ba16a25c..78968b4a 100644 --- a/.devcontainer/post-install.sh +++ b/.devcontainer/post-install.sh @@ -53,7 +53,12 @@ if [ ! -f "$DEVCONTAINER_JSON" ];then fi # open devcontainer -HOST_PATH=$(echo $(wslpath -w $PWD) | sed -e 's,\\,\\\\,g') +if command -v wslpath >/dev/null 2>&1; then + HOST_PATH=$(echo $(wslpath -w $PWD) | sed -e 's,\\,\\\\,g') +else + # Not on WSL, fallback for macOS/Linux + HOST_PATH="$PWD" +fi WORKSPACE="/workspaces/$(basename $PWD)" URI_SUFFIX= From 28a5921564867f496069b76bc50467986bcaf637 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 11 Jun 2025 14:54:05 -0400 Subject: [PATCH 017/141] Add build step --- .devcontainer/post-install.sh | 29 ++++------------------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/.devcontainer/post-install.sh b/.devcontainer/post-install.sh index 78968b4a..5c06f57b 100644 --- a/.devcontainer/post-install.sh +++ b/.devcontainer/post-install.sh @@ -1,32 +1,11 @@ #!/usr/bin/env bash -if [ "$1" == "--help" ]; then - - cat << EOF -Run from project folder, auto opens vscode in some mode depending on folder contents: -* Folder contains .devcontainer/devcontainer.json and .code-workspace file: vscode opens in devcontainer, workspace file is loaded -* Folder contains .devcontainer/devcontainer.json: vscode opens in devcontainer -* Folder contains .code-workspace file: Workspace is opened in vscode -* Folder contains no .code-workspace and no devcontainer: vscode is opened, loading contents of the current folder +# Run /scripts/build.sh +./scripts/build.sh -This script was created for WSL2, probably works the same way for native Linux, but untested - -Assumes the following filestructure: - - -| -| -- .code-workspace -| -- ./devcontainer/devcontainer.json -| -- ... - -Note: If you set workspaceFolder or workspaceMount in devcontainer.json this may cause issues - Also, if .devcontainer/devcontainer.json is not in the root of your repository, you may get in trouble - refer to https://code.visualstudio.com/remote/advancedcontainers/change-default-source-mount - -EOF - exit 0 -fi +# Open VSCode .code-workspace file +# https://gist.github.com/Kaptensanders/79da7c1547751fb43c75904e3110bbf9 # check for dependencies if ! command -v xxd &> /dev/null; then From 9f9953be64ebe661ce911571328e643bd4382d3d Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 11 Jun 2025 14:59:32 -0400 Subject: [PATCH 018/141] abs path --- .devcontainer/post-install.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.devcontainer/post-install.sh b/.devcontainer/post-install.sh index 5c06f57b..a4dd2dc5 100644 --- a/.devcontainer/post-install.sh +++ b/.devcontainer/post-install.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +# Setup .env.local +echo "PYTHON_BIN=python" > /workspaces/cua/.env.local # Run /scripts/build.sh ./scripts/build.sh From 19c92d8051cd156950c8f85c5e12f5b1edbe43d1 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 11 Jun 2025 15:05:17 -0400 Subject: [PATCH 019/141] bump python version --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index ac9531af..7762b43d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.11-slim +FROM python:3.12-slim # Set environment variables ENV PYTHONUNBUFFERED=1 \ From 00497e28ab2fa496c8c33278e9d24af3ea135e68 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 11 Jun 2025 15:12:34 -0400 Subject: [PATCH 020/141] fix code not found --- .devcontainer/post-install.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.devcontainer/post-install.sh b/.devcontainer/post-install.sh index a4dd2dc5..d041262a 100644 --- a/.devcontainer/post-install.sh +++ b/.devcontainer/post-install.sh @@ -56,4 +56,6 @@ fi URI="{\"hostPath\":\"$HOST_PATH\",\"configFile\":{\"\$mid\":1,\"path\":\"$DEVCONTAINER_JSON\",\"scheme\":\"vscode-fileHost\"}}" URI_HEX=$(echo "${URI}" | xxd -c 0 -p) + +alias code="$(ls ~/.vscode-server*/bin/*/bin/code-server* | head -n 1)" code ${URI_TYPE}="vscode-remote://dev-container%2B${URI_HEX}${URI_SUFFIX}" & \ No newline at end of file From 017eea0f8966fb0a51033729d797c27b2af05102 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 11 Jun 2025 15:21:01 -0400 Subject: [PATCH 021/141] Updated code alias --- .devcontainer/post-install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.devcontainer/post-install.sh b/.devcontainer/post-install.sh index d041262a..ebcd9953 100644 --- a/.devcontainer/post-install.sh +++ b/.devcontainer/post-install.sh @@ -57,5 +57,5 @@ fi URI="{\"hostPath\":\"$HOST_PATH\",\"configFile\":{\"\$mid\":1,\"path\":\"$DEVCONTAINER_JSON\",\"scheme\":\"vscode-fileHost\"}}" URI_HEX=$(echo "${URI}" | xxd -c 0 -p) -alias code="$(ls ~/.vscode-server*/bin/*/bin/code-server* | head -n 1)" +export code="$(ls ~/.vscode-server*/bin/*/bin/remote-cli* | head -n 1)" code ${URI_TYPE}="vscode-remote://dev-container%2B${URI_HEX}${URI_SUFFIX}" & \ No newline at end of file From 949ed187528e53d4d86b5b79c0754cfd2c524c24 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 11 Jun 2025 15:26:22 -0400 Subject: [PATCH 022/141] debug --- .devcontainer/post-install.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.devcontainer/post-install.sh b/.devcontainer/post-install.sh index ebcd9953..61852f78 100644 --- a/.devcontainer/post-install.sh +++ b/.devcontainer/post-install.sh @@ -57,5 +57,16 @@ fi URI="{\"hostPath\":\"$HOST_PATH\",\"configFile\":{\"\$mid\":1,\"path\":\"$DEVCONTAINER_JSON\",\"scheme\":\"vscode-fileHost\"}}" URI_HEX=$(echo "${URI}" | xxd -c 0 -p) +echo "ls ~/.vscode-server*/bin/*/bin/remote-cli* yields:" +ls ~/.vscode-server*/bin/*/bin/remote-cli* || echo "No match found" + export code="$(ls ~/.vscode-server*/bin/*/bin/remote-cli* | head -n 1)" +echo "exported code (from home): $code" + +echo "ls /vscode/vscode-server/bin/*/bin/remote-cli* yields:" +ls /vscode/vscode-server/bin/*/bin/remote-cli* || echo "No match found" + +export code_v2="$(ls /vscode/vscode-server/bin/*/bin/remote-cli* | head -n 1)" +echo "exported code_v2 (from /vscode): $code_v2" + code ${URI_TYPE}="vscode-remote://dev-container%2B${URI_HEX}${URI_SUFFIX}" & \ No newline at end of file From 6e6e0cbc639167f9dd347ee4daf67e02faed6781 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 11 Jun 2025 15:46:20 -0400 Subject: [PATCH 023/141] Fix code path --- .devcontainer/post-install.sh | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/.devcontainer/post-install.sh b/.devcontainer/post-install.sh index 61852f78..0d9a293d 100644 --- a/.devcontainer/post-install.sh +++ b/.devcontainer/post-install.sh @@ -57,16 +57,5 @@ fi URI="{\"hostPath\":\"$HOST_PATH\",\"configFile\":{\"\$mid\":1,\"path\":\"$DEVCONTAINER_JSON\",\"scheme\":\"vscode-fileHost\"}}" URI_HEX=$(echo "${URI}" | xxd -c 0 -p) -echo "ls ~/.vscode-server*/bin/*/bin/remote-cli* yields:" -ls ~/.vscode-server*/bin/*/bin/remote-cli* || echo "No match found" - -export code="$(ls ~/.vscode-server*/bin/*/bin/remote-cli* | head -n 1)" -echo "exported code (from home): $code" - -echo "ls /vscode/vscode-server/bin/*/bin/remote-cli* yields:" -ls /vscode/vscode-server/bin/*/bin/remote-cli* || echo "No match found" - -export code_v2="$(ls /vscode/vscode-server/bin/*/bin/remote-cli* | head -n 1)" -echo "exported code_v2 (from /vscode): $code_v2" - -code ${URI_TYPE}="vscode-remote://dev-container%2B${URI_HEX}${URI_SUFFIX}" & \ No newline at end of file +export code="$(ls /vscode/vscode-server/bin/*/*/bin/remote-cli/code 2>/dev/null | head -n 1)" +"$code" ${URI_TYPE}="vscode-remote://dev-container%2B${URI_HEX}${URI_SUFFIX}" & \ No newline at end of file From 2245b0e29ef64f9b15c6c2f3afd69bf256adce35 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 11 Jun 2025 18:56:54 -0400 Subject: [PATCH 024/141] simplified post-install --- .devcontainer/post-install.sh | 50 ++++------------------------------- 1 file changed, 5 insertions(+), 45 deletions(-) diff --git a/.devcontainer/post-install.sh b/.devcontainer/post-install.sh index 0d9a293d..5292a259 100644 --- a/.devcontainer/post-install.sh +++ b/.devcontainer/post-install.sh @@ -1,14 +1,13 @@ #!/usr/bin/env bash +WORKSPACE="/workspaces/cua" + # Setup .env.local echo "PYTHON_BIN=python" > /workspaces/cua/.env.local # Run /scripts/build.sh ./scripts/build.sh -# Open VSCode .code-workspace file -# https://gist.github.com/Kaptensanders/79da7c1547751fb43c75904e3110bbf9 - # check for dependencies if ! command -v xxd &> /dev/null; then echo "xxd command not found, install with" @@ -16,46 +15,7 @@ if ! command -v xxd &> /dev/null; then exit 1 fi -DEVCONTAINER_JSON="$PWD/.devcontainer/devcontainer.json" -CODE_WS_FILE=$(ls $PWD/*.code-workspace 2>/dev/null) - -if [ ! -f "$DEVCONTAINER_JSON" ];then - - # open code without container - - if [ -f "$CODE_WS_FILE" ]; then - echo "Opening vscode workspace from $CODE_WS_FILE" - code $CODE_WS_FILE - else - echo "Opening vscode in current directory" - code . - fi - exit 0 -fi - -# open devcontainer -if command -v wslpath >/dev/null 2>&1; then - HOST_PATH=$(echo $(wslpath -w $PWD) | sed -e 's,\\,\\\\,g') -else - # Not on WSL, fallback for macOS/Linux - HOST_PATH="$PWD" -fi -WORKSPACE="/workspaces/$(basename $PWD)" - -URI_SUFFIX= -if [ -f "$CODE_WS_FILE" ]; then - # open workspace file - URI_TYPE="--file-uri" - URI_SUFFIX="$WORKSPACE/$(basename $CODE_WS_FILE)" - echo "Opening vscode workspace file within devcontainer" -else - URI_TYPE="--folder-uri" - URI_SUFFIX="$WORKSPACE" - echo "Opening vscode within devcontainer" -fi - -URI="{\"hostPath\":\"$HOST_PATH\",\"configFile\":{\"\$mid\":1,\"path\":\"$DEVCONTAINER_JSON\",\"scheme\":\"vscode-fileHost\"}}" -URI_HEX=$(echo "${URI}" | xxd -c 0 -p) - +CODE_WS_FILE="$WORKSPACE/.vscode/py.code-workspace" export code="$(ls /vscode/vscode-server/bin/*/*/bin/remote-cli/code 2>/dev/null | head -n 1)" -"$code" ${URI_TYPE}="vscode-remote://dev-container%2B${URI_HEX}${URI_SUFFIX}" & \ No newline at end of file + +"$code" $CODE_WS_FILE & From 989f294b1950f316cbc5089ec66e984284d5dede Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 11 Jun 2025 22:52:01 -0400 Subject: [PATCH 025/141] Add workspace post install --- .devcontainer/post-install.sh | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/.devcontainer/post-install.sh b/.devcontainer/post-install.sh index 5292a259..1738e635 100644 --- a/.devcontainer/post-install.sh +++ b/.devcontainer/post-install.sh @@ -8,14 +8,21 @@ echo "PYTHON_BIN=python" > /workspaces/cua/.env.local # Run /scripts/build.sh ./scripts/build.sh -# check for dependencies -if ! command -v xxd &> /dev/null; then - echo "xxd command not found, install with" - echo "sudo apt install xxd" - exit 1 -fi +# --- +# Build is complete. Show user a clear message to open the workspace manually. +# --- -CODE_WS_FILE="$WORKSPACE/.vscode/py.code-workspace" -export code="$(ls /vscode/vscode-server/bin/*/*/bin/remote-cli/code 2>/dev/null | head -n 1)" +cat << 'EOM' -"$code" $CODE_WS_FILE & +============================================ + 🚀 Build complete! + + 👉 Next steps: + + 1. Open '.vscode/py.code-workspace' + 2. Press 'Open Workspace' + + Happy coding! +============================================ + +EOM From 594e741ae96447dfdfbfac149307fc55325873aa Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 11 Jun 2025 23:03:00 -0400 Subject: [PATCH 026/141] Updated readme with dev container --- README.md | 56 +++++++++++++++++++++++++------------------------------ 1 file changed, 25 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 14652769..763e14b4 100644 --- a/README.md +++ b/README.md @@ -54,49 +54,43 @@ ### Option 1: Fully-managed install (recommended) -*I want to be totally guided in the process* +*Guided install for quick use* **macOS/Linux/Windows (via WSL):** ```bash # Requires Python 3.11+ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/scripts/playground.sh)" ``` +This script will guide you through setup and launch the Computer-Use Agent UI. -This script will: -- Ask if you want to use local VMs or C/ua Cloud Containers -- Install necessary dependencies (Lume CLI for local VMs) -- Download VM images if needed -- Install Python packages -- Launch the Computer-Use Agent UI +--- -### Option 2: Key manual steps -
-If you are skeptical running one-install scripts +### Option 2: [Dev Container](./.devcontainer/README.md) +*Best for contributors and development* -**For C/ua Agent UI (any system, cloud VMs only):** +This repository includes a [Dev Container](./.devcontainer/README.md) configuration that simplifies setup to a few steps: + +1. **Install Dev Containers extension** in VS Code +2. **Clone and open in container:** + - Press `Ctrl+Shift+P` (or `Cmd+Shift+P` on Mac) + - Type `Dev Containers: Clone Repository in Container Volume...` + - Paste the repository URL: `https://github.com/trycua/cua.git` + - Open the `.vscode/py.code-workspace` workspace +3. **Run the Agent UI example:** Once the container builds, you're ready to run the Agent UI example! + + +The Gradio UI will be available at `http://localhost:7860` and will automatically forward to your host machine. + +**Environment Variables:** +You'll need to add your API keys to `.env.local`: ```bash -# Requires Python 3.11+ and C/ua API key -pip install -U "cua-computer[all]" "cua-agent[all]" -python -m agent.ui.gradio.app +# Required for Anthropic provider +ANTHROPIC_API_KEY=your_anthropic_key_here + +# Required for OpenAI provider +OPENAI_API_KEY=your_openai_key_here ``` -**For Local macOS/Linux VMs (Apple Silicon only):** -```bash -# 1. Install Lume CLI -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/libs/lume/scripts/install.sh)" - -# 2. Pull macOS image -lume pull macos-sequoia-cua:latest - -# 3. Start VM -lume run macos-sequoia-cua:latest - -# 4. Install packages and launch UI -pip install -U "cua-computer[all]" "cua-agent[all]" -python -m agent.ui.gradio.app -``` -
- --- *How it works: Computer module provides secure desktops (Lume CLI locally, [C/ua Cloud Containers](https://trycua.com) remotely), Agent module provides local/API agents with OpenAI AgentResponse format and [trajectory tracing](https://trycua.com/trajectory-viewer).* From 2b67b63e1b6125b8999ae6281ebdda4a7a20cff0 Mon Sep 17 00:00:00 2001 From: ddupont <3820588+ddupont808@users.noreply.github.com> Date: Wed, 11 Jun 2025 23:06:51 -0400 Subject: [PATCH 027/141] Update README.md --- README.md | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/README.md b/README.md index 763e14b4..d40b9aa1 100644 --- a/README.md +++ b/README.md @@ -76,21 +76,10 @@ This repository includes a [Dev Container](./.devcontainer/README.md) configurat - Type `Dev Containers: Clone Repository in Container Volume...` - Paste the repository URL: `https://github.com/trycua/cua.git` - Open the `.vscode/py.code-workspace` workspace -3. **Run the Agent UI example:** Once the container builds, you're ready to run the Agent UI example! - +3. **Run the Agent UI example:** Click image to start the Gradio UI The Gradio UI will be available at `http://localhost:7860` and will automatically forward to your host machine. -**Environment Variables:** -You'll need to add your API keys to `.env.local`: -```bash -# Required for Anthropic provider -ANTHROPIC_API_KEY=your_anthropic_key_here - -# Required for OpenAI provider -OPENAI_API_KEY=your_openai_key_here -``` - --- *How it works: Computer module provides secure desktops (Lume CLI locally, [C/ua Cloud Containers](https://trycua.com) remotely), Agent module provides local/API agents with OpenAI AgentResponse format and [trajectory tracing](https://trycua.com/trajectory-viewer).* From 3cdc08dabd7e47090add9151607d87340e39fe5c Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Thu, 12 Jun 2025 19:55:58 -0400 Subject: [PATCH 028/141] Initial computer typescript library creation --- libs/computer/{ => python}/README.md | 0 .../{ => python}/computer/__init__.py | 0 .../{ => python}/computer/computer.py | 0 .../{ => python}/computer/diorama_computer.py | 0 .../computer/{ => python}/computer/helpers.py | 0 .../computer/interface/__init__.py | 0 .../{ => python}/computer/interface/base.py | 0 .../computer/interface/factory.py | 0 .../{ => python}/computer/interface/linux.py | 0 .../{ => python}/computer/interface/macos.py | 0 .../{ => python}/computer/interface/models.py | 0 libs/computer/{ => python}/computer/logger.py | 0 libs/computer/{ => python}/computer/models.py | 0 .../computer/providers/__init__.py | 0 .../{ => python}/computer/providers/base.py | 0 .../computer/providers/cloud/__init__.py | 0 .../computer/providers/cloud/provider.py | 0 .../computer/providers/factory.py | 0 .../computer/providers/lume/__init__.py | 0 .../computer/providers/lume/provider.py | 0 .../computer/providers/lume_api.py | 0 .../computer/providers/lumier/__init__.py | 0 .../computer/providers/lumier/provider.py | 0 .../{ => python}/computer/telemetry.py | 0 .../{ => python}/computer/ui/__init__.py | 0 .../computer/ui/gradio/__init__.py | 0 .../{ => python}/computer/ui/gradio/app.py | 0 libs/computer/{ => python}/computer/utils.py | 0 libs/computer/{ => python}/poetry.toml | 0 libs/computer/{ => python}/pyproject.toml | 0 libs/computer/typescript/.editorconfig | 6 + libs/computer/typescript/.gitattributes | 1 + libs/computer/typescript/.github/FUNDING.yml | 1 + .../typescript/.github/renovate.json5 | 4 + .../typescript/.github/workflows/release.yml | 26 + .../.github/workflows/unit-test.yml | 38 + libs/computer/typescript/.gitignore | 6 + libs/computer/typescript/.nvmrc | 1 + libs/computer/typescript/LICENSE | 21 + libs/computer/typescript/README.md | 119 + libs/computer/typescript/biome.json | 86 + libs/computer/typescript/package.json | 54 + libs/computer/typescript/pnpm-lock.yaml | 2000 +++++++++++++++++ libs/computer/typescript/pnpm-workspace.yaml | 3 + .../typescript/src/computer/computer.ts | 644 ++++++ .../computer/typescript/src/computer/index.ts | 2 + libs/computer/typescript/src/helpers.ts | 76 + libs/computer/typescript/src/index.ts | 28 + .../computer/typescript/src/interface/base.ts | 41 + .../typescript/src/interface/factory.ts | 56 + .../typescript/src/interface/index.ts | 5 + .../typescript/src/interface/linux.ts | 47 + .../typescript/src/interface/macos.ts | 47 + .../typescript/src/interface/models.ts | 96 + libs/computer/typescript/src/logger.ts | 50 + libs/computer/typescript/src/models.ts | 21 + .../computer/typescript/src/providers/base.ts | 140 ++ .../typescript/src/providers/cloud/index.ts | 5 + .../src/providers/cloud/provider.ts | 68 + .../typescript/src/providers/factory.ts | 150 ++ .../typescript/src/providers/index.ts | 12 + .../typescript/src/providers/lume/index.ts | 16 + .../typescript/src/providers/lume/provider.ts | 182 ++ .../typescript/src/providers/lume_api.ts | 265 +++ .../typescript/src/providers/lumier/index.ts | 16 + .../src/providers/lumier/provider.ts | 401 ++++ libs/computer/typescript/src/telemetry.ts | 115 + libs/computer/typescript/src/utils.ts | 118 + libs/computer/typescript/tsconfig.json | 22 + libs/computer/typescript/tsdown.config.ts | 10 + libs/computer/typescript/vitest.config.ts | 3 + 71 files changed, 5002 insertions(+) rename libs/computer/{ => python}/README.md (100%) rename libs/computer/{ => python}/computer/__init__.py (100%) rename libs/computer/{ => python}/computer/computer.py (100%) rename libs/computer/{ => python}/computer/diorama_computer.py (100%) rename libs/computer/{ => python}/computer/helpers.py (100%) rename libs/computer/{ => python}/computer/interface/__init__.py (100%) rename libs/computer/{ => python}/computer/interface/base.py (100%) rename libs/computer/{ => python}/computer/interface/factory.py (100%) rename libs/computer/{ => python}/computer/interface/linux.py (100%) rename libs/computer/{ => python}/computer/interface/macos.py (100%) rename libs/computer/{ => python}/computer/interface/models.py (100%) rename libs/computer/{ => python}/computer/logger.py (100%) rename libs/computer/{ => python}/computer/models.py (100%) rename libs/computer/{ => python}/computer/providers/__init__.py (100%) rename libs/computer/{ => python}/computer/providers/base.py (100%) rename libs/computer/{ => python}/computer/providers/cloud/__init__.py (100%) rename libs/computer/{ => python}/computer/providers/cloud/provider.py (100%) rename libs/computer/{ => python}/computer/providers/factory.py (100%) rename libs/computer/{ => python}/computer/providers/lume/__init__.py (100%) rename libs/computer/{ => python}/computer/providers/lume/provider.py (100%) rename libs/computer/{ => python}/computer/providers/lume_api.py (100%) rename libs/computer/{ => python}/computer/providers/lumier/__init__.py (100%) rename libs/computer/{ => python}/computer/providers/lumier/provider.py (100%) rename libs/computer/{ => python}/computer/telemetry.py (100%) rename libs/computer/{ => python}/computer/ui/__init__.py (100%) rename libs/computer/{ => python}/computer/ui/gradio/__init__.py (100%) rename libs/computer/{ => python}/computer/ui/gradio/app.py (100%) rename libs/computer/{ => python}/computer/utils.py (100%) rename libs/computer/{ => python}/poetry.toml (100%) rename libs/computer/{ => python}/pyproject.toml (100%) create mode 100644 libs/computer/typescript/.editorconfig create mode 100644 libs/computer/typescript/.gitattributes create mode 100644 libs/computer/typescript/.github/FUNDING.yml create mode 100644 libs/computer/typescript/.github/renovate.json5 create mode 100644 libs/computer/typescript/.github/workflows/release.yml create mode 100644 libs/computer/typescript/.github/workflows/unit-test.yml create mode 100644 libs/computer/typescript/.gitignore create mode 100644 libs/computer/typescript/.nvmrc create mode 100644 libs/computer/typescript/LICENSE create mode 100644 libs/computer/typescript/README.md create mode 100644 libs/computer/typescript/biome.json create mode 100644 libs/computer/typescript/package.json create mode 100644 libs/computer/typescript/pnpm-lock.yaml create mode 100644 libs/computer/typescript/pnpm-workspace.yaml create mode 100644 libs/computer/typescript/src/computer/computer.ts create mode 100644 libs/computer/typescript/src/computer/index.ts create mode 100644 libs/computer/typescript/src/helpers.ts create mode 100644 libs/computer/typescript/src/index.ts create mode 100644 libs/computer/typescript/src/interface/base.ts create mode 100644 libs/computer/typescript/src/interface/factory.ts create mode 100644 libs/computer/typescript/src/interface/index.ts create mode 100644 libs/computer/typescript/src/interface/linux.ts create mode 100644 libs/computer/typescript/src/interface/macos.ts create mode 100644 libs/computer/typescript/src/interface/models.ts create mode 100644 libs/computer/typescript/src/logger.ts create mode 100644 libs/computer/typescript/src/models.ts create mode 100644 libs/computer/typescript/src/providers/base.ts create mode 100644 libs/computer/typescript/src/providers/cloud/index.ts create mode 100644 libs/computer/typescript/src/providers/cloud/provider.ts create mode 100644 libs/computer/typescript/src/providers/factory.ts create mode 100644 libs/computer/typescript/src/providers/index.ts create mode 100644 libs/computer/typescript/src/providers/lume/index.ts create mode 100644 libs/computer/typescript/src/providers/lume/provider.ts create mode 100644 libs/computer/typescript/src/providers/lume_api.ts create mode 100644 libs/computer/typescript/src/providers/lumier/index.ts create mode 100644 libs/computer/typescript/src/providers/lumier/provider.ts create mode 100644 libs/computer/typescript/src/telemetry.ts create mode 100644 libs/computer/typescript/src/utils.ts create mode 100644 libs/computer/typescript/tsconfig.json create mode 100644 libs/computer/typescript/tsdown.config.ts create mode 100644 libs/computer/typescript/vitest.config.ts diff --git a/libs/computer/README.md b/libs/computer/python/README.md similarity index 100% rename from libs/computer/README.md rename to libs/computer/python/README.md diff --git a/libs/computer/computer/__init__.py b/libs/computer/python/computer/__init__.py similarity index 100% rename from libs/computer/computer/__init__.py rename to libs/computer/python/computer/__init__.py diff --git a/libs/computer/computer/computer.py b/libs/computer/python/computer/computer.py similarity index 100% rename from libs/computer/computer/computer.py rename to libs/computer/python/computer/computer.py diff --git a/libs/computer/computer/diorama_computer.py b/libs/computer/python/computer/diorama_computer.py similarity index 100% rename from libs/computer/computer/diorama_computer.py rename to libs/computer/python/computer/diorama_computer.py diff --git a/libs/computer/computer/helpers.py b/libs/computer/python/computer/helpers.py similarity index 100% rename from libs/computer/computer/helpers.py rename to libs/computer/python/computer/helpers.py diff --git a/libs/computer/computer/interface/__init__.py b/libs/computer/python/computer/interface/__init__.py similarity index 100% rename from libs/computer/computer/interface/__init__.py rename to libs/computer/python/computer/interface/__init__.py diff --git a/libs/computer/computer/interface/base.py b/libs/computer/python/computer/interface/base.py similarity index 100% rename from libs/computer/computer/interface/base.py rename to libs/computer/python/computer/interface/base.py diff --git a/libs/computer/computer/interface/factory.py b/libs/computer/python/computer/interface/factory.py similarity index 100% rename from libs/computer/computer/interface/factory.py rename to libs/computer/python/computer/interface/factory.py diff --git a/libs/computer/computer/interface/linux.py b/libs/computer/python/computer/interface/linux.py similarity index 100% rename from libs/computer/computer/interface/linux.py rename to libs/computer/python/computer/interface/linux.py diff --git a/libs/computer/computer/interface/macos.py b/libs/computer/python/computer/interface/macos.py similarity index 100% rename from libs/computer/computer/interface/macos.py rename to libs/computer/python/computer/interface/macos.py diff --git a/libs/computer/computer/interface/models.py b/libs/computer/python/computer/interface/models.py similarity index 100% rename from libs/computer/computer/interface/models.py rename to libs/computer/python/computer/interface/models.py diff --git a/libs/computer/computer/logger.py b/libs/computer/python/computer/logger.py similarity index 100% rename from libs/computer/computer/logger.py rename to libs/computer/python/computer/logger.py diff --git a/libs/computer/computer/models.py b/libs/computer/python/computer/models.py similarity index 100% rename from libs/computer/computer/models.py rename to libs/computer/python/computer/models.py diff --git a/libs/computer/computer/providers/__init__.py b/libs/computer/python/computer/providers/__init__.py similarity index 100% rename from libs/computer/computer/providers/__init__.py rename to libs/computer/python/computer/providers/__init__.py diff --git a/libs/computer/computer/providers/base.py b/libs/computer/python/computer/providers/base.py similarity index 100% rename from libs/computer/computer/providers/base.py rename to libs/computer/python/computer/providers/base.py diff --git a/libs/computer/computer/providers/cloud/__init__.py b/libs/computer/python/computer/providers/cloud/__init__.py similarity index 100% rename from libs/computer/computer/providers/cloud/__init__.py rename to libs/computer/python/computer/providers/cloud/__init__.py diff --git a/libs/computer/computer/providers/cloud/provider.py b/libs/computer/python/computer/providers/cloud/provider.py similarity index 100% rename from libs/computer/computer/providers/cloud/provider.py rename to libs/computer/python/computer/providers/cloud/provider.py diff --git a/libs/computer/computer/providers/factory.py b/libs/computer/python/computer/providers/factory.py similarity index 100% rename from libs/computer/computer/providers/factory.py rename to libs/computer/python/computer/providers/factory.py diff --git a/libs/computer/computer/providers/lume/__init__.py b/libs/computer/python/computer/providers/lume/__init__.py similarity index 100% rename from libs/computer/computer/providers/lume/__init__.py rename to libs/computer/python/computer/providers/lume/__init__.py diff --git a/libs/computer/computer/providers/lume/provider.py b/libs/computer/python/computer/providers/lume/provider.py similarity index 100% rename from libs/computer/computer/providers/lume/provider.py rename to libs/computer/python/computer/providers/lume/provider.py diff --git a/libs/computer/computer/providers/lume_api.py b/libs/computer/python/computer/providers/lume_api.py similarity index 100% rename from libs/computer/computer/providers/lume_api.py rename to libs/computer/python/computer/providers/lume_api.py diff --git a/libs/computer/computer/providers/lumier/__init__.py b/libs/computer/python/computer/providers/lumier/__init__.py similarity index 100% rename from libs/computer/computer/providers/lumier/__init__.py rename to libs/computer/python/computer/providers/lumier/__init__.py diff --git a/libs/computer/computer/providers/lumier/provider.py b/libs/computer/python/computer/providers/lumier/provider.py similarity index 100% rename from libs/computer/computer/providers/lumier/provider.py rename to libs/computer/python/computer/providers/lumier/provider.py diff --git a/libs/computer/computer/telemetry.py b/libs/computer/python/computer/telemetry.py similarity index 100% rename from libs/computer/computer/telemetry.py rename to libs/computer/python/computer/telemetry.py diff --git a/libs/computer/computer/ui/__init__.py b/libs/computer/python/computer/ui/__init__.py similarity index 100% rename from libs/computer/computer/ui/__init__.py rename to libs/computer/python/computer/ui/__init__.py diff --git a/libs/computer/computer/ui/gradio/__init__.py b/libs/computer/python/computer/ui/gradio/__init__.py similarity index 100% rename from libs/computer/computer/ui/gradio/__init__.py rename to libs/computer/python/computer/ui/gradio/__init__.py diff --git a/libs/computer/computer/ui/gradio/app.py b/libs/computer/python/computer/ui/gradio/app.py similarity index 100% rename from libs/computer/computer/ui/gradio/app.py rename to libs/computer/python/computer/ui/gradio/app.py diff --git a/libs/computer/computer/utils.py b/libs/computer/python/computer/utils.py similarity index 100% rename from libs/computer/computer/utils.py rename to libs/computer/python/computer/utils.py diff --git a/libs/computer/poetry.toml b/libs/computer/python/poetry.toml similarity index 100% rename from libs/computer/poetry.toml rename to libs/computer/python/poetry.toml diff --git a/libs/computer/pyproject.toml b/libs/computer/python/pyproject.toml similarity index 100% rename from libs/computer/pyproject.toml rename to libs/computer/python/pyproject.toml diff --git a/libs/computer/typescript/.editorconfig b/libs/computer/typescript/.editorconfig new file mode 100644 index 00000000..7095e7fb --- /dev/null +++ b/libs/computer/typescript/.editorconfig @@ -0,0 +1,6 @@ +root = true + +[*] +indent_size = 2 +end_of_line = lf +insert_final_newline = true diff --git a/libs/computer/typescript/.gitattributes b/libs/computer/typescript/.gitattributes new file mode 100644 index 00000000..6313b56c --- /dev/null +++ b/libs/computer/typescript/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/libs/computer/typescript/.github/FUNDING.yml b/libs/computer/typescript/.github/FUNDING.yml new file mode 100644 index 00000000..c4630bc2 --- /dev/null +++ b/libs/computer/typescript/.github/FUNDING.yml @@ -0,0 +1 @@ +github: sxzz diff --git a/libs/computer/typescript/.github/renovate.json5 b/libs/computer/typescript/.github/renovate.json5 new file mode 100644 index 00000000..ac1c0dc9 --- /dev/null +++ b/libs/computer/typescript/.github/renovate.json5 @@ -0,0 +1,4 @@ +{ + extends: ['github>sxzz/renovate-config'], + automerge: true, +} diff --git a/libs/computer/typescript/.github/workflows/release.yml b/libs/computer/typescript/.github/workflows/release.yml new file mode 100644 index 00000000..e5b7a4c3 --- /dev/null +++ b/libs/computer/typescript/.github/workflows/release.yml @@ -0,0 +1,26 @@ +name: Release + +permissions: + contents: write + +on: + push: + tags: + - 'v*' + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set node + uses: actions/setup-node@v4 + with: + node-version: lts/* + + - run: npx changelogithub + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/libs/computer/typescript/.github/workflows/unit-test.yml b/libs/computer/typescript/.github/workflows/unit-test.yml new file mode 100644 index 00000000..dc3418c5 --- /dev/null +++ b/libs/computer/typescript/.github/workflows/unit-test.yml @@ -0,0 +1,38 @@ +name: Unit Test + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4.1.0 + + - name: Set node LTS + uses: actions/setup-node@v4 + with: + node-version: lts/* + cache: pnpm + + - name: Install + run: pnpm install + + - name: Build + run: pnpm run build + + - name: Lint + run: pnpm run lint + + - name: Typecheck + run: pnpm run typecheck + + - name: Test + run: pnpm run test diff --git a/libs/computer/typescript/.gitignore b/libs/computer/typescript/.gitignore new file mode 100644 index 00000000..e79f2036 --- /dev/null +++ b/libs/computer/typescript/.gitignore @@ -0,0 +1,6 @@ +node_modules +dist + +*.log +.DS_Store +.eslintcache diff --git a/libs/computer/typescript/.nvmrc b/libs/computer/typescript/.nvmrc new file mode 100644 index 00000000..dc864a05 --- /dev/null +++ b/libs/computer/typescript/.nvmrc @@ -0,0 +1 @@ +v24.2.0 diff --git a/libs/computer/typescript/LICENSE b/libs/computer/typescript/LICENSE new file mode 100644 index 00000000..7ff04379 --- /dev/null +++ b/libs/computer/typescript/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright © 2025 C/UA + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/libs/computer/typescript/README.md b/libs/computer/typescript/README.md new file mode 100644 index 00000000..5ed0f1ac --- /dev/null +++ b/libs/computer/typescript/README.md @@ -0,0 +1,119 @@ +# C/UA Computer TypeScript Library + +The TypeScript library for C/UA Computer - a powerful computer control and automation library. + +## Overview + +This library is a TypeScript port of the Python computer library, providing the same functionality for controlling virtual machines and computer interfaces. It includes: + +- **Computer Class**: Main class for interacting with computers (virtual or host) +- **VM Providers**: Support for different VM providers (Lume, Lumier, Cloud) +- **Computer Interfaces**: OS-specific interfaces for controlling computers (macOS, Linux, Windows) +- **Utilities**: Helper functions for display parsing, memory parsing, logging, and telemetry + +## Installation + +```bash +npm install @cua/computer +# or +pnpm add @cua/computer +``` + +## Usage + +```typescript +import { Computer } from '@cua/computer'; + +// Create a new computer instance +const computer = new Computer({ + display: '1024x768', + memory: '8GB', + cpu: '4', + osType: 'macos', + image: 'macos-sequoia-cua:latest' +}); + +// Start the computer +await computer.run(); + +// Get the computer interface for interaction +const interface = computer.interface; + +// Take a screenshot +const screenshot = await interface.getScreenshot(); + +// Click at coordinates +await interface.click(500, 300); + +// Type text +await interface.typeText('Hello, world!'); + +// Stop the computer +await computer.stop(); +``` + +## Architecture + +The library is organized into several key modules: + +### Core Components +- `Computer`: Main class that manages VM lifecycle and interfaces +- `ComputerOptions`: Configuration options for computer instances + +### Models +- `Display`: Display configuration (width, height) +- `ComputerConfig`: Internal computer configuration + +### Providers +- `BaseVMProvider`: Abstract base class for VM providers +- `VMProviderFactory`: Factory for creating provider instances +- Provider types: `LUME`, `LUMIER`, `CLOUD` + +### Interfaces +- `BaseComputerInterface`: Abstract base class for OS interfaces +- `InterfaceFactory`: Factory for creating OS-specific interfaces +- Interface models: Key types, mouse buttons, accessibility tree + +### Utilities +- `Logger`: Logging with different verbosity levels +- `helpers`: Default computer management and sandboxed execution +- `utils`: Display/memory parsing, timeout utilities +- `telemetry`: Usage tracking and metrics + +## Development + +- Install dependencies: + +```bash +pnpm install +``` + +- Run the unit tests: + +```bash +pnpm test +``` + +- Build the library: + +```bash +pnpm build +``` + +- Type checking: + +```bash +pnpm typecheck +``` + +## External Dependencies + +- `sharp`: For image processing and screenshot manipulation +- Additional provider-specific packages need to be installed separately: + - `@cua/computer-lume`: For Lume provider support + - `@cua/computer-lumier`: For Lumier provider support + - `@cua/computer-cloud`: For Cloud provider support + +## License + +[MIT](./LICENSE) License 2025 [C/UA](https://github.com/trycua) diff --git a/libs/computer/typescript/biome.json b/libs/computer/typescript/biome.json new file mode 100644 index 00000000..221019d2 --- /dev/null +++ b/libs/computer/typescript/biome.json @@ -0,0 +1,86 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "ignore": [ + "build", + "node_modules" + ] + }, + "formatter": { + "enabled": true, + "useEditorconfig": true, + "formatWithErrors": false, + "indentStyle": "space", + "indentWidth": 2, + "lineEnding": "lf", + "lineWidth": 80, + "attributePosition": "auto", + "bracketSpacing": true + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "style": { + "useSelfClosingElements": "warn", + "noUnusedTemplateLiteral": "warn", + "noNonNullAssertion": "off" + }, + "a11y": { + "useMediaCaption": "off", + "useKeyWithClickEvents": "warn", + "useKeyWithMouseEvents": "warn", + "noSvgWithoutTitle": "off", + "useButtonType": "warn", + "noAutofocus": "off" + }, + "suspicious": { + "noArrayIndexKey": "off" + }, + "correctness": { + "noUnusedVariables": "warn", + "noUnusedFunctionParameters": "warn", + "noUnusedImports": "warn" + }, + "complexity": { + "useOptionalChain": "info" + }, + "nursery": { + "useSortedClasses": { + "level": "warn", + "fix": "safe", + "options": { + "attributes": [ + "className" + ], + "functions": [ + "cn" + ] + } + } + } + } + }, + "javascript": { + "formatter": { + "jsxQuoteStyle": "double", + "quoteProperties": "asNeeded", + "trailingCommas": "es5", + "semicolons": "always", + "arrowParentheses": "always", + "bracketSameLine": false, + "quoteStyle": "single", + "attributePosition": "auto", + "bracketSpacing": true + } + } +} \ No newline at end of file diff --git a/libs/computer/typescript/package.json b/libs/computer/typescript/package.json new file mode 100644 index 00000000..3727210b --- /dev/null +++ b/libs/computer/typescript/package.json @@ -0,0 +1,54 @@ +{ + "name": "@cua/computer", + "version": "0.0.0", + "packageManager": "pnpm@10.11.0", + "description": "", + "type": "module", + "license": "MIT", + "homepage": "", + "bugs": { + "url": "" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/trycua/cua.git" + }, + "author": "", + "funding": "", + "files": [ + "dist" + ], + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": "./dist/index.js", + "./package.json": "./package.json" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "lint": "biome lint --cache .", + "lint:fix": "biome lint --cache --fix .", + "build": "tsdown", + "dev": "tsdown --watch", + "test": "vitest", + "typecheck": "tsc --noEmit", + "release": "bumpp && pnpm publish", + "prepublishOnly": "pnpm run build" + }, + "dependencies": { + "sharp": "^0.33.0" + }, + "devDependencies": { + "@biomejs/biome": "^1.9.4", + "@types/node": "^22.15.17", + "bumpp": "^10.1.0", + "happy-dom": "^17.4.7", + "tsdown": "^0.11.9", + "tsx": "^4.19.4", + "typescript": "^5.8.3", + "vitest": "^3.1.3" + } +} diff --git a/libs/computer/typescript/pnpm-lock.yaml b/libs/computer/typescript/pnpm-lock.yaml new file mode 100644 index 00000000..12d841f9 --- /dev/null +++ b/libs/computer/typescript/pnpm-lock.yaml @@ -0,0 +1,2000 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + sharp: + specifier: ^0.33.0 + version: 0.33.5 + devDependencies: + '@biomejs/biome': + specifier: ^1.9.4 + version: 1.9.4 + '@types/node': + specifier: ^22.15.17 + version: 22.15.31 + bumpp: + specifier: ^10.1.0 + version: 10.1.1 + happy-dom: + specifier: ^17.4.7 + version: 17.6.3 + tsdown: + specifier: ^0.11.9 + version: 0.11.13(typescript@5.8.3) + tsx: + specifier: ^4.19.4 + version: 4.20.2 + typescript: + specifier: ^5.8.3 + version: 5.8.3 + vitest: + specifier: ^3.1.3 + version: 3.2.3(@types/debug@4.1.12)(@types/node@22.15.31)(happy-dom@17.6.3)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0) + +packages: + + '@babel/generator@7.27.5': + resolution: {integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.27.5': + resolution: {integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.27.6': + resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==} + engines: {node: '>=6.9.0'} + + '@biomejs/biome@1.9.4': + resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@1.9.4': + resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@1.9.4': + resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@1.9.4': + resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@1.9.4': + resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@1.9.4': + resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@1.9.4': + resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@1.9.4': + resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@1.9.4': + resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + + '@emnapi/core@1.4.3': + resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} + + '@emnapi/runtime@1.4.3': + resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} + + '@emnapi/wasi-threads@1.0.2': + resolution: {integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==} + + '@esbuild/aix-ppc64@0.25.5': + resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.5': + resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.5': + resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.5': + resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.5': + resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.5': + resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.5': + resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.5': + resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.5': + resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.5': + resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.5': + resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.5': + resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.5': + resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.5': + resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.5': + resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.5': + resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.5': + resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.5': + resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.5': + resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.5': + resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.5': + resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.25.5': + resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.5': + resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.5': + resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.5': + resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@img/sharp-darwin-arm64@0.33.5': + resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.33.5': + resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.0.4': + resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.0.4': + resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.0.4': + resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.0.5': + resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.0.4': + resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.0.4': + resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.33.5': + resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.33.5': + resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-s390x@0.33.5': + resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.33.5': + resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.33.5': + resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.33.5': + resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.33.5': + resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-ia32@0.33.5': + resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.33.5': + resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@napi-rs/wasm-runtime@0.2.11': + resolution: {integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==} + + '@oxc-project/types@0.70.0': + resolution: {integrity: sha512-ngyLUpUjO3dpqygSRQDx7nMx8+BmXbWOU4oIwTJFV2MVIDG7knIZwgdwXlQWLg3C3oxg1lS7ppMtPKqKFb7wzw==} + + '@quansync/fs@0.1.3': + resolution: {integrity: sha512-G0OnZbMWEs5LhDyqy2UL17vGhSVHkQIfVojMtEWVenvj0V5S84VBgy86kJIuNsGDp2p7sTKlpSIpBUWdC35OKg==} + engines: {node: '>=20.0.0'} + + '@rolldown/binding-darwin-arm64@1.0.0-beta.9': + resolution: {integrity: sha512-geUG/FUpm+membLC0NQBb39vVyOfguYZ2oyXc7emr6UjH6TeEECT4b0CPZXKFnELareTiU/Jfl70/eEgNxyQeA==} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-beta.9': + resolution: {integrity: sha512-7wPXDwcOtv2I+pWTL2UNpNAxMAGukgBT90Jz4DCfwaYdGvQncF7J0S7IWrRVsRFhBavxM+65RcueE3VXw5UIbg==} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-beta.9': + resolution: {integrity: sha512-agO5mONTNKVrcIt4SRxw5Ni0FOVV3gaH8dIiNp1A4JeU91b9kw7x+JRuNJAQuM2X3pYqVvA6qh13UTNOsaqM/Q==} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9': + resolution: {integrity: sha512-dDNDV9p/8WYDriS9HCcbH6y6+JP38o3enj/pMkdkmkxEnZ0ZoHIfQ9RGYWeRYU56NKBCrya4qZBJx49Jk9LRug==} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9': + resolution: {integrity: sha512-kZKegmHG1ZvfsFIwYU6DeFSxSIcIliXzeznsJHUo9D9/dlVSDi/PUvsRKcuJkQjZoejM6pk8MHN/UfgGdIhPHw==} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.9': + resolution: {integrity: sha512-f+VL8mO31pyMJiJPr2aA1ryYONkP2UqgbwK7fKtKHZIeDd/AoUGn3+ujPqDhuy2NxgcJ5H8NaSvDpG1tJMHh+g==} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.9': + resolution: {integrity: sha512-GiUEZ0WPjX5LouDoC3O8aJa4h6BLCpIvaAboNw5JoRour/3dC6rbtZZ/B5FC3/ySsN3/dFOhAH97ylQxoZJi7A==} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-musl@1.0.0-beta.9': + resolution: {integrity: sha512-AMb0dicw+QHh6RxvWo4BRcuTMgS0cwUejJRMpSyIcHYnKTbj6nUW4HbWNQuDfZiF27l6F5gEwBS+YLUdVzL9vg==} + cpu: [x64] + os: [linux] + + '@rolldown/binding-wasm32-wasi@1.0.0-beta.9': + resolution: {integrity: sha512-+pdaiTx7L8bWKvsAuCE0HAxP1ze1WOLoWGCawcrZbMSY10dMh2i82lJiH6tXGXbfYYwsNWhWE2NyG4peFZvRfQ==} + engines: {node: '>=14.21.3'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9': + resolution: {integrity: sha512-A7kN248viWvb8eZMzQu024TBKGoyoVYBsDG2DtoP8u2pzwoh5yDqUL291u01o4f8uzpUHq8mfwQJmcGChFu8KQ==} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9': + resolution: {integrity: sha512-DzKN7iEYjAP8AK8F2G2aCej3fk43Y/EQrVrR3gF0XREes56chjQ7bXIhw819jv74BbxGdnpPcslhet/cgt7WRA==} + cpu: [ia32] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.9': + resolution: {integrity: sha512-GMWgTvvbZ8TfBsAiJpoz4SRq3IN3aUMn0rYm8q4I8dcEk4J1uISyfb6ZMzvqW+cvScTWVKWZNqnrmYOKLLUt4w==} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.0-beta.9': + resolution: {integrity: sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==} + + '@rollup/rollup-android-arm-eabi@4.43.0': + resolution: {integrity: sha512-Krjy9awJl6rKbruhQDgivNbD1WuLb8xAclM4IR4cN5pHGAs2oIMMQJEiC3IC/9TZJ+QZkmZhlMO/6MBGxPidpw==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.43.0': + resolution: {integrity: sha512-ss4YJwRt5I63454Rpj+mXCXicakdFmKnUNxr1dLK+5rv5FJgAxnN7s31a5VchRYxCFWdmnDWKd0wbAdTr0J5EA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.43.0': + resolution: {integrity: sha512-eKoL8ykZ7zz8MjgBenEF2OoTNFAPFz1/lyJ5UmmFSz5jW+7XbH1+MAgCVHy72aG59rbuQLcJeiMrP8qP5d/N0A==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.43.0': + resolution: {integrity: sha512-SYwXJgaBYW33Wi/q4ubN+ldWC4DzQY62S4Ll2dgfr/dbPoF50dlQwEaEHSKrQdSjC6oIe1WgzosoaNoHCdNuMg==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.43.0': + resolution: {integrity: sha512-SV+U5sSo0yujrjzBF7/YidieK2iF6E7MdF6EbYxNz94lA+R0wKl3SiixGyG/9Klab6uNBIqsN7j4Y/Fya7wAjQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.43.0': + resolution: {integrity: sha512-J7uCsiV13L/VOeHJBo5SjasKiGxJ0g+nQTrBkAsmQBIdil3KhPnSE9GnRon4ejX1XDdsmK/l30IYLiAaQEO0Cg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.43.0': + resolution: {integrity: sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.43.0': + resolution: {integrity: sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.43.0': + resolution: {integrity: sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.43.0': + resolution: {integrity: sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.43.0': + resolution: {integrity: sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.43.0': + resolution: {integrity: sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.43.0': + resolution: {integrity: sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.43.0': + resolution: {integrity: sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.43.0': + resolution: {integrity: sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.43.0': + resolution: {integrity: sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.43.0': + resolution: {integrity: sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.43.0': + resolution: {integrity: sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.43.0': + resolution: {integrity: sha512-fYCTEyzf8d+7diCw8b+asvWDCLMjsCEA8alvtAutqJOJp/wL5hs1rWSqJ1vkjgW0L2NB4bsYJrpKkiIPRR9dvw==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.43.0': + resolution: {integrity: sha512-SnGhLiE5rlK0ofq8kzuDkM0g7FN1s5VYY+YSMTibP7CqShxCQvqtNxTARS4xX4PFJfHjG0ZQYX9iGzI3FQh5Aw==} + cpu: [x64] + os: [win32] + + '@tybys/wasm-util@0.9.0': + resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} + + '@types/chai@5.2.2': + resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.7': + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@22.15.31': + resolution: {integrity: sha512-jnVe5ULKl6tijxUhvQeNbQG/84fHfg+yMak02cT8QVhBx/F05rAVxCGBYYTh2EKz22D6JF5ktXuNwdx7b9iEGw==} + + '@vitest/expect@3.2.3': + resolution: {integrity: sha512-W2RH2TPWVHA1o7UmaFKISPvdicFJH+mjykctJFoAkUw+SPTJTGjUNdKscFBrqM7IPnCVu6zihtKYa7TkZS1dkQ==} + + '@vitest/mocker@3.2.3': + resolution: {integrity: sha512-cP6fIun+Zx8he4rbWvi+Oya6goKQDZK+Yq4hhlggwQBbrlOQ4qtZ+G4nxB6ZnzI9lyIb+JnvyiJnPC2AGbKSPA==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.3': + resolution: {integrity: sha512-yFglXGkr9hW/yEXngO+IKMhP0jxyFw2/qys/CK4fFUZnSltD+MU7dVYGrH8rvPcK/O6feXQA+EU33gjaBBbAng==} + + '@vitest/runner@3.2.3': + resolution: {integrity: sha512-83HWYisT3IpMaU9LN+VN+/nLHVBCSIUKJzGxC5RWUOsK1h3USg7ojL+UXQR3b4o4UBIWCYdD2fxuzM7PQQ1u8w==} + + '@vitest/snapshot@3.2.3': + resolution: {integrity: sha512-9gIVWx2+tysDqUmmM1L0hwadyumqssOL1r8KJipwLx5JVYyxvVRfxvMq7DaWbZZsCqZnu/dZedaZQh4iYTtneA==} + + '@vitest/spy@3.2.3': + resolution: {integrity: sha512-JHu9Wl+7bf6FEejTCREy+DmgWe+rQKbK+y32C/k5f4TBIAlijhJbRBIRIOCEpVevgRsCQR2iHRUH2/qKVM/plw==} + + '@vitest/utils@3.2.3': + resolution: {integrity: sha512-4zFBCU5Pf+4Z6v+rwnZ1HU1yzOKKvDkMXZrymE2PBlbjKJRlrOxbvpfPSvJTGRIwGoahaOGvp+kbCoxifhzJ1Q==} + + ansis@4.1.0: + resolution: {integrity: sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==} + engines: {node: '>=14'} + + args-tokenizer@0.3.0: + resolution: {integrity: sha512-xXAd7G2Mll5W8uo37GETpQ2VrE84M181Z7ugHFGQnJZ50M2mbOv0osSZ9VsSgPfJQ+LVG0prSi0th+ELMsno7Q==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-kit@2.1.0: + resolution: {integrity: sha512-ROM2LlXbZBZVk97crfw8PGDOBzzsJvN2uJCmwswvPUNyfH14eg90mSN3xNqsri1JS1G9cz0VzeDUhxJkTrr4Ew==} + engines: {node: '>=20.18.0'} + + birpc@2.3.0: + resolution: {integrity: sha512-ijbtkn/F3Pvzb6jHypHRyve2QApOCZDR25D/VnkY2G/lBNcXCTsnsCxgY4k4PkVB7zfwzYbY3O9Lcqe3xufS5g==} + + bumpp@10.1.1: + resolution: {integrity: sha512-69ejE1J5O5qDN3oRu2jRas1nQmi5zEYepjzbYPpi1znuDnp+zZ9Yezsf/nYauWeoMNALQ5toniNGET05Txj2cQ==} + engines: {node: '>=18'} + hasBin: true + + c12@3.0.4: + resolution: {integrity: sha512-t5FaZTYbbCtvxuZq9xxIruYydrAGsJ+8UdP0pZzMiK2xl/gNiSOy0OxhLzHUEEb0m1QXYqfzfvyIFEmz/g9lqg==} + peerDependencies: + magicast: ^0.3.5 + peerDependenciesMeta: + magicast: + optional: true + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + chai@5.2.0: + resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} + engines: {node: '>=12'} + + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + + detect-libc@2.0.4: + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} + engines: {node: '>=8'} + + diff@8.0.2: + resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} + engines: {node: '>=0.3.1'} + + dotenv@16.5.0: + resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} + engines: {node: '>=12'} + + dts-resolver@2.1.1: + resolution: {integrity: sha512-3BiGFhB6mj5Kv+W2vdJseQUYW+SKVzAFJL6YNP6ursbrwy1fXHRotfHi3xLNxe4wZl/K8qbAFeCDjZLjzqxxRw==} + engines: {node: '>=20.18.0'} + peerDependencies: + oxc-resolver: '>=11.0.0' + peerDependenciesMeta: + oxc-resolver: + optional: true + + empathic@1.1.0: + resolution: {integrity: sha512-rsPft6CK3eHtrlp9Y5ALBb+hfK+DWnA4WFebbazxjWyx8vSm3rZeoM3z9irsjcqO3PYRzlfv27XIB4tz2DV7RA==} + engines: {node: '>=14'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + esbuild@0.25.5: + resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.2.1: + resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} + engines: {node: '>=12.0.0'} + + exsolve@1.0.5: + resolution: {integrity: sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==} + + fdir@6.4.6: + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-tsconfig@4.10.1: + resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + + giget@2.0.0: + resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} + hasBin: true + + happy-dom@17.6.3: + resolution: {integrity: sha512-UVIHeVhxmxedbWPCfgS55Jg2rDfwf2BCKeylcPSqazLz5w3Kri7Q4xdBJubsr/+VUzFLh0VjIvh13RaDA2/Xug==} + engines: {node: '>=20.0.0'} + + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + + jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + hasBin: true + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + + loupe@3.1.3: + resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-fetch-native@1.6.6: + resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==} + + nypm@0.6.0: + resolution: {integrity: sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg==} + engines: {node: ^14.16.0 || >=16.10.0} + hasBin: true + + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + + package-manager-detector@1.3.0: + resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} + + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + + pkg-types@2.1.0: + resolution: {integrity: sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==} + + postcss@8.5.5: + resolution: {integrity: sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==} + engines: {node: ^10 || ^12 || >=14} + + quansync@0.2.10: + resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} + + rc9@2.1.2: + resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + rolldown-plugin-dts@0.13.11: + resolution: {integrity: sha512-1TScN31JImk8xcq9kdm52z2W8/QX3zeDpEjFkyZmK+GcD0u8QqSWWARBsCEdfS99NyI6D9NIbUpsABXlcpZhig==} + engines: {node: '>=20.18.0'} + peerDependencies: + '@typescript/native-preview': '>=7.0.0-dev.20250601.1' + rolldown: ^1.0.0-beta.9 + typescript: ^5.0.0 + vue-tsc: ~2.2.0 + peerDependenciesMeta: + '@typescript/native-preview': + optional: true + typescript: + optional: true + vue-tsc: + optional: true + + rolldown@1.0.0-beta.9: + resolution: {integrity: sha512-ZgZky52n6iF0UainGKjptKGrOG4Con2S5sdc4C4y2Oj25D5PHAY8Y8E5f3M2TSd/zlhQs574JlMeTe3vREczSg==} + hasBin: true + peerDependencies: + '@oxc-project/runtime': 0.70.0 + peerDependenciesMeta: + '@oxc-project/runtime': + optional: true + + rollup@4.43.0: + resolution: {integrity: sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + sharp@0.33.5: + resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.9.0: + resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + + strip-literal@3.0.0: + resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyexec@1.0.1: + resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} + + tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.0: + resolution: {integrity: sha512-7CotroY9a8DKsKprEy/a14aCCm8jYVmR7aFy4fpkZM8sdpNJbKkixuNjgM50yCmip2ezc8z4N7k3oe2+rfRJCQ==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.3: + resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} + engines: {node: '>=14.0.0'} + + tsdown@0.11.13: + resolution: {integrity: sha512-VSfoNm8MJXFdg7PJ4p2javgjMRiQQHpkP9N3iBBTrmCixcT6YZ9ZtqYMW3NDHczqR0C0Qnur1HMQr1ZfZcmrng==} + engines: {node: '>=18.0.0'} + hasBin: true + peerDependencies: + publint: ^0.3.0 + typescript: ^5.0.0 + unplugin-lightningcss: ^0.4.0 + unplugin-unused: ^0.5.0 + peerDependenciesMeta: + publint: + optional: true + typescript: + optional: true + unplugin-lightningcss: + optional: true + unplugin-unused: + optional: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.20.2: + resolution: {integrity: sha512-He0ZWr41gLa4vD30Au3yuwpe0HXaCZbclvl8RBieUiJ9aFnPMWUPIyvw3RU8+1Crjfcrauvitae2a4tUzRAGsw==} + engines: {node: '>=18.0.0'} + hasBin: true + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + unconfig@7.3.2: + resolution: {integrity: sha512-nqG5NNL2wFVGZ0NA/aCFw0oJ2pxSf1lwg4Z5ill8wd7K4KX/rQbHlwbh+bjctXL5Ly1xtzHenHGOK0b+lG6JVg==} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + vite-node@3.2.3: + resolution: {integrity: sha512-gc8aAifGuDIpZHrPjuHyP4dpQmYXqWw7D1GmDnWeNWP654UEXzVfQ5IHPSK5HaHkwB/+p1atpYpSdw/2kOv8iQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite@6.3.5: + resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.2.3: + resolution: {integrity: sha512-E6U2ZFXe3N/t4f5BwUaVCKRLHqUpk1CBWeMh78UT4VaTPH/2dyvH6ALl29JTovEPu9dVKr/K/J4PkXgrMbw4Ww==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.3 + '@vitest/ui': 3.2.3 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-mimetype@3.0.0: + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + yaml@2.8.0: + resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==} + engines: {node: '>= 14.6'} + hasBin: true + +snapshots: + + '@babel/generator@7.27.5': + dependencies: + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.1.0 + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/parser@7.27.5': + dependencies: + '@babel/types': 7.27.6 + + '@babel/types@7.27.6': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@biomejs/biome@1.9.4': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 1.9.4 + '@biomejs/cli-darwin-x64': 1.9.4 + '@biomejs/cli-linux-arm64': 1.9.4 + '@biomejs/cli-linux-arm64-musl': 1.9.4 + '@biomejs/cli-linux-x64': 1.9.4 + '@biomejs/cli-linux-x64-musl': 1.9.4 + '@biomejs/cli-win32-arm64': 1.9.4 + '@biomejs/cli-win32-x64': 1.9.4 + + '@biomejs/cli-darwin-arm64@1.9.4': + optional: true + + '@biomejs/cli-darwin-x64@1.9.4': + optional: true + + '@biomejs/cli-linux-arm64-musl@1.9.4': + optional: true + + '@biomejs/cli-linux-arm64@1.9.4': + optional: true + + '@biomejs/cli-linux-x64-musl@1.9.4': + optional: true + + '@biomejs/cli-linux-x64@1.9.4': + optional: true + + '@biomejs/cli-win32-arm64@1.9.4': + optional: true + + '@biomejs/cli-win32-x64@1.9.4': + optional: true + + '@emnapi/core@1.4.3': + dependencies: + '@emnapi/wasi-threads': 1.0.2 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.4.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.0.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.25.5': + optional: true + + '@esbuild/android-arm64@0.25.5': + optional: true + + '@esbuild/android-arm@0.25.5': + optional: true + + '@esbuild/android-x64@0.25.5': + optional: true + + '@esbuild/darwin-arm64@0.25.5': + optional: true + + '@esbuild/darwin-x64@0.25.5': + optional: true + + '@esbuild/freebsd-arm64@0.25.5': + optional: true + + '@esbuild/freebsd-x64@0.25.5': + optional: true + + '@esbuild/linux-arm64@0.25.5': + optional: true + + '@esbuild/linux-arm@0.25.5': + optional: true + + '@esbuild/linux-ia32@0.25.5': + optional: true + + '@esbuild/linux-loong64@0.25.5': + optional: true + + '@esbuild/linux-mips64el@0.25.5': + optional: true + + '@esbuild/linux-ppc64@0.25.5': + optional: true + + '@esbuild/linux-riscv64@0.25.5': + optional: true + + '@esbuild/linux-s390x@0.25.5': + optional: true + + '@esbuild/linux-x64@0.25.5': + optional: true + + '@esbuild/netbsd-arm64@0.25.5': + optional: true + + '@esbuild/netbsd-x64@0.25.5': + optional: true + + '@esbuild/openbsd-arm64@0.25.5': + optional: true + + '@esbuild/openbsd-x64@0.25.5': + optional: true + + '@esbuild/sunos-x64@0.25.5': + optional: true + + '@esbuild/win32-arm64@0.25.5': + optional: true + + '@esbuild/win32-ia32@0.25.5': + optional: true + + '@esbuild/win32-x64@0.25.5': + optional: true + + '@img/sharp-darwin-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.0.4 + optional: true + + '@img/sharp-darwin-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.0.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.0.5': + optional: true + + '@img/sharp-libvips-linux-s390x@1.0.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + optional: true + + '@img/sharp-linux-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.0.4 + optional: true + + '@img/sharp-linux-arm@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.0.5 + optional: true + + '@img/sharp-linux-s390x@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.0.4 + optional: true + + '@img/sharp-linux-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + optional: true + + '@img/sharp-wasm32@0.33.5': + dependencies: + '@emnapi/runtime': 1.4.3 + optional: true + + '@img/sharp-win32-ia32@0.33.5': + optional: true + + '@img/sharp-win32-x64@0.33.5': + optional: true + + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@napi-rs/wasm-runtime@0.2.11': + dependencies: + '@emnapi/core': 1.4.3 + '@emnapi/runtime': 1.4.3 + '@tybys/wasm-util': 0.9.0 + optional: true + + '@oxc-project/types@0.70.0': {} + + '@quansync/fs@0.1.3': + dependencies: + quansync: 0.2.10 + + '@rolldown/binding-darwin-arm64@1.0.0-beta.9': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-beta.9': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-beta.9': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.9': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.9': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-beta.9': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-beta.9': + dependencies: + '@napi-rs/wasm-runtime': 0.2.11 + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9': + optional: true + + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.9': + optional: true + + '@rolldown/pluginutils@1.0.0-beta.9': {} + + '@rollup/rollup-android-arm-eabi@4.43.0': + optional: true + + '@rollup/rollup-android-arm64@4.43.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.43.0': + optional: true + + '@rollup/rollup-darwin-x64@4.43.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.43.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.43.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.43.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.43.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.43.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.43.0': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.43.0': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.43.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.43.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.43.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.43.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.43.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.43.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.43.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.43.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.43.0': + optional: true + + '@tybys/wasm-util@0.9.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/chai@5.2.2': + dependencies: + '@types/deep-eql': 4.0.2 + + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + optional: true + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.7': {} + + '@types/estree@1.0.8': {} + + '@types/ms@2.1.0': + optional: true + + '@types/node@22.15.31': + dependencies: + undici-types: 6.21.0 + + '@vitest/expect@3.2.3': + dependencies: + '@types/chai': 5.2.2 + '@vitest/spy': 3.2.3 + '@vitest/utils': 3.2.3 + chai: 5.2.0 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.3(vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0))': + dependencies: + '@vitest/spy': 3.2.3 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 6.3.5(@types/node@22.15.31)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0) + + '@vitest/pretty-format@3.2.3': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.3': + dependencies: + '@vitest/utils': 3.2.3 + pathe: 2.0.3 + strip-literal: 3.0.0 + + '@vitest/snapshot@3.2.3': + dependencies: + '@vitest/pretty-format': 3.2.3 + magic-string: 0.30.17 + pathe: 2.0.3 + + '@vitest/spy@3.2.3': + dependencies: + tinyspy: 4.0.3 + + '@vitest/utils@3.2.3': + dependencies: + '@vitest/pretty-format': 3.2.3 + loupe: 3.1.3 + tinyrainbow: 2.0.0 + + ansis@4.1.0: {} + + args-tokenizer@0.3.0: {} + + assertion-error@2.0.1: {} + + ast-kit@2.1.0: + dependencies: + '@babel/parser': 7.27.5 + pathe: 2.0.3 + + birpc@2.3.0: {} + + bumpp@10.1.1: + dependencies: + ansis: 4.1.0 + args-tokenizer: 0.3.0 + c12: 3.0.4 + cac: 6.7.14 + escalade: 3.2.0 + jsonc-parser: 3.3.1 + package-manager-detector: 1.3.0 + semver: 7.7.2 + tinyexec: 1.0.1 + tinyglobby: 0.2.14 + yaml: 2.8.0 + transitivePeerDependencies: + - magicast + + c12@3.0.4: + dependencies: + chokidar: 4.0.3 + confbox: 0.2.2 + defu: 6.1.4 + dotenv: 16.5.0 + exsolve: 1.0.5 + giget: 2.0.0 + jiti: 2.4.2 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 1.0.0 + pkg-types: 2.1.0 + rc9: 2.1.2 + + cac@6.7.14: {} + + chai@5.2.0: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.3 + pathval: 2.0.0 + + check-error@2.1.1: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + citty@0.1.6: + dependencies: + consola: 3.4.2 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + + confbox@0.2.2: {} + + consola@3.4.2: {} + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + deep-eql@5.0.2: {} + + defu@6.1.4: {} + + destr@2.0.5: {} + + detect-libc@2.0.4: {} + + diff@8.0.2: {} + + dotenv@16.5.0: {} + + dts-resolver@2.1.1: {} + + empathic@1.1.0: {} + + es-module-lexer@1.7.0: {} + + esbuild@0.25.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.5 + '@esbuild/android-arm': 0.25.5 + '@esbuild/android-arm64': 0.25.5 + '@esbuild/android-x64': 0.25.5 + '@esbuild/darwin-arm64': 0.25.5 + '@esbuild/darwin-x64': 0.25.5 + '@esbuild/freebsd-arm64': 0.25.5 + '@esbuild/freebsd-x64': 0.25.5 + '@esbuild/linux-arm': 0.25.5 + '@esbuild/linux-arm64': 0.25.5 + '@esbuild/linux-ia32': 0.25.5 + '@esbuild/linux-loong64': 0.25.5 + '@esbuild/linux-mips64el': 0.25.5 + '@esbuild/linux-ppc64': 0.25.5 + '@esbuild/linux-riscv64': 0.25.5 + '@esbuild/linux-s390x': 0.25.5 + '@esbuild/linux-x64': 0.25.5 + '@esbuild/netbsd-arm64': 0.25.5 + '@esbuild/netbsd-x64': 0.25.5 + '@esbuild/openbsd-arm64': 0.25.5 + '@esbuild/openbsd-x64': 0.25.5 + '@esbuild/sunos-x64': 0.25.5 + '@esbuild/win32-arm64': 0.25.5 + '@esbuild/win32-ia32': 0.25.5 + '@esbuild/win32-x64': 0.25.5 + + escalade@3.2.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + expect-type@1.2.1: {} + + exsolve@1.0.5: {} + + fdir@6.4.6(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + + fsevents@2.3.3: + optional: true + + get-tsconfig@4.10.1: + dependencies: + resolve-pkg-maps: 1.0.0 + + giget@2.0.0: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + defu: 6.1.4 + node-fetch-native: 1.6.6 + nypm: 0.6.0 + pathe: 2.0.3 + + happy-dom@17.6.3: + dependencies: + webidl-conversions: 7.0.0 + whatwg-mimetype: 3.0.0 + + hookable@5.5.3: {} + + is-arrayish@0.3.2: {} + + jiti@2.4.2: {} + + js-tokens@9.0.1: {} + + jsesc@3.1.0: {} + + jsonc-parser@3.3.1: {} + + loupe@3.1.3: {} + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + node-fetch-native@1.6.6: {} + + nypm@0.6.0: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + pathe: 2.0.3 + pkg-types: 2.1.0 + tinyexec: 0.3.2 + + ohash@2.0.11: {} + + package-manager-detector@1.3.0: {} + + pathe@2.0.3: {} + + pathval@2.0.0: {} + + perfect-debounce@1.0.0: {} + + picocolors@1.1.1: {} + + picomatch@4.0.2: {} + + pkg-types@2.1.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.5 + pathe: 2.0.3 + + postcss@8.5.5: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + quansync@0.2.10: {} + + rc9@2.1.2: + dependencies: + defu: 6.1.4 + destr: 2.0.5 + + readdirp@4.1.2: {} + + resolve-pkg-maps@1.0.0: {} + + rolldown-plugin-dts@0.13.11(rolldown@1.0.0-beta.9)(typescript@5.8.3): + dependencies: + '@babel/generator': 7.27.5 + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 + ast-kit: 2.1.0 + birpc: 2.3.0 + debug: 4.4.1 + dts-resolver: 2.1.1 + get-tsconfig: 4.10.1 + rolldown: 1.0.0-beta.9 + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - oxc-resolver + - supports-color + + rolldown@1.0.0-beta.9: + dependencies: + '@oxc-project/types': 0.70.0 + '@rolldown/pluginutils': 1.0.0-beta.9 + ansis: 4.1.0 + optionalDependencies: + '@rolldown/binding-darwin-arm64': 1.0.0-beta.9 + '@rolldown/binding-darwin-x64': 1.0.0-beta.9 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.9 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.9 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.9 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.9 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.9 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.9 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.9 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.9 + '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.9 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.9 + + rollup@4.43.0: + dependencies: + '@types/estree': 1.0.7 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.43.0 + '@rollup/rollup-android-arm64': 4.43.0 + '@rollup/rollup-darwin-arm64': 4.43.0 + '@rollup/rollup-darwin-x64': 4.43.0 + '@rollup/rollup-freebsd-arm64': 4.43.0 + '@rollup/rollup-freebsd-x64': 4.43.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.43.0 + '@rollup/rollup-linux-arm-musleabihf': 4.43.0 + '@rollup/rollup-linux-arm64-gnu': 4.43.0 + '@rollup/rollup-linux-arm64-musl': 4.43.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.43.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.43.0 + '@rollup/rollup-linux-riscv64-gnu': 4.43.0 + '@rollup/rollup-linux-riscv64-musl': 4.43.0 + '@rollup/rollup-linux-s390x-gnu': 4.43.0 + '@rollup/rollup-linux-x64-gnu': 4.43.0 + '@rollup/rollup-linux-x64-musl': 4.43.0 + '@rollup/rollup-win32-arm64-msvc': 4.43.0 + '@rollup/rollup-win32-ia32-msvc': 4.43.0 + '@rollup/rollup-win32-x64-msvc': 4.43.0 + fsevents: 2.3.3 + + semver@7.7.2: {} + + sharp@0.33.5: + dependencies: + color: 4.2.3 + detect-libc: 2.0.4 + semver: 7.7.2 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-s390x': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-wasm32': 0.33.5 + '@img/sharp-win32-ia32': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 + + siginfo@2.0.0: {} + + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + std-env@3.9.0: {} + + strip-literal@3.0.0: + dependencies: + js-tokens: 9.0.1 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyexec@1.0.1: {} + + tinyglobby@0.2.14: + dependencies: + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 + + tinypool@1.1.0: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.3: {} + + tsdown@0.11.13(typescript@5.8.3): + dependencies: + ansis: 4.1.0 + cac: 6.7.14 + chokidar: 4.0.3 + debug: 4.4.1 + diff: 8.0.2 + empathic: 1.1.0 + hookable: 5.5.3 + rolldown: 1.0.0-beta.9 + rolldown-plugin-dts: 0.13.11(rolldown@1.0.0-beta.9)(typescript@5.8.3) + semver: 7.7.2 + tinyexec: 1.0.1 + tinyglobby: 0.2.14 + unconfig: 7.3.2 + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - '@oxc-project/runtime' + - '@typescript/native-preview' + - oxc-resolver + - supports-color + - vue-tsc + + tslib@2.8.1: + optional: true + + tsx@4.20.2: + dependencies: + esbuild: 0.25.5 + get-tsconfig: 4.10.1 + optionalDependencies: + fsevents: 2.3.3 + + typescript@5.8.3: {} + + unconfig@7.3.2: + dependencies: + '@quansync/fs': 0.1.3 + defu: 6.1.4 + jiti: 2.4.2 + quansync: 0.2.10 + + undici-types@6.21.0: {} + + vite-node@3.2.3(@types/node@22.15.31)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0): + dependencies: + cac: 6.7.14 + debug: 4.4.1 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 6.3.5(@types/node@22.15.31)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0): + dependencies: + esbuild: 0.25.5 + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 + postcss: 8.5.5 + rollup: 4.43.0 + tinyglobby: 0.2.14 + optionalDependencies: + '@types/node': 22.15.31 + fsevents: 2.3.3 + jiti: 2.4.2 + tsx: 4.20.2 + yaml: 2.8.0 + + vitest@3.2.3(@types/debug@4.1.12)(@types/node@22.15.31)(happy-dom@17.6.3)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0): + dependencies: + '@types/chai': 5.2.2 + '@vitest/expect': 3.2.3 + '@vitest/mocker': 3.2.3(vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0)) + '@vitest/pretty-format': 3.2.3 + '@vitest/runner': 3.2.3 + '@vitest/snapshot': 3.2.3 + '@vitest/spy': 3.2.3 + '@vitest/utils': 3.2.3 + chai: 5.2.0 + debug: 4.4.1 + expect-type: 1.2.1 + magic-string: 0.30.17 + pathe: 2.0.3 + picomatch: 4.0.2 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.14 + tinypool: 1.1.0 + tinyrainbow: 2.0.0 + vite: 6.3.5(@types/node@22.15.31)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0) + vite-node: 3.2.3(@types/node@22.15.31)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.12 + '@types/node': 22.15.31 + happy-dom: 17.6.3 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + webidl-conversions@7.0.0: {} + + whatwg-mimetype@3.0.0: {} + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + yaml@2.8.0: {} diff --git a/libs/computer/typescript/pnpm-workspace.yaml b/libs/computer/typescript/pnpm-workspace.yaml new file mode 100644 index 00000000..e9796de5 --- /dev/null +++ b/libs/computer/typescript/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +onlyBuiltDependencies: + - "@biomejs/biome" + - sharp diff --git a/libs/computer/typescript/src/computer/computer.ts b/libs/computer/typescript/src/computer/computer.ts new file mode 100644 index 00000000..f4c9cffd --- /dev/null +++ b/libs/computer/typescript/src/computer/computer.ts @@ -0,0 +1,644 @@ +import type { Display, ComputerConfig } from '../models'; +import type { BaseComputerInterface } from '../interface/base'; +import { InterfaceFactory } from '../interface/factory'; +import type { BaseVMProvider } from '../providers/base'; +import { VMProviderType } from '../providers/base'; +import { VMProviderFactory } from '../providers/factory'; +import { Logger, LogLevel } from '../logger'; +import { recordComputerInitialization, recordVMStart, recordVMStop } from '../telemetry'; +import { setDefaultComputer } from '../helpers'; +import { parseDisplayString, parseImageString, sleep, withTimeout } from '../utils'; +import sharp from 'sharp'; + +export type OSType = 'macos' | 'linux' | 'windows'; + +export interface ComputerOptions { + display?: Display | { width: number; height: number } | string; + memory?: string; + cpu?: string; + osType?: OSType; + name?: string; + image?: string; + sharedDirectories?: string[]; + useHostComputerServer?: boolean; + verbosity?: number | LogLevel; + telemetryEnabled?: boolean; + providerType?: VMProviderType | string; + port?: number; + noVNCPort?: number; + host?: string; + storage?: string; + ephemeral?: boolean; + apiKey?: string; + experiments?: string[]; +} + +/** + * Computer is the main class for interacting with the computer. + */ +export class Computer { + private logger: Logger; + private vmLogger: Logger; + private interfaceLogger: Logger; + + private image: string; + private port?: number; + private noVNCPort?: number; + private host: string; + private osType: OSType; + private providerType: VMProviderType | string; + private ephemeral: boolean; + private apiKey?: string; + private experiments: string[]; + private storage?: string; + private sharedPath?: string; + private sharedDirectories: string[]; + private _telemetryEnabled: boolean; + private _initialized: boolean = false; + private _running: boolean = false; + private verbosity: number | LogLevel; + private useHostComputerServer: boolean; + private config?: ComputerConfig; + private _providerContext?: BaseVMProvider; + private _interface?: BaseComputerInterface; + private _stopEvent?: Promise; + private _keepAliveTask?: Promise; + + /** + * Initialize a new Computer instance. + * + * @param options Configuration options for the Computer + */ + constructor(options: ComputerOptions = {}) { + const { + display = '1024x768', + memory = '8GB', + cpu = '4', + osType = 'macos', + name = '', + image = 'macos-sequoia-cua:latest', + sharedDirectories = [], + useHostComputerServer = false, + verbosity = LogLevel.NORMAL, + telemetryEnabled = true, + providerType = VMProviderType.LUME, + port = 7777, + noVNCPort = 8006, + host = process.env.PYLUME_HOST || 'localhost', + storage, + ephemeral = false, + apiKey, + experiments = [] + } = options; + + this.verbosity = verbosity; + this.logger = new Logger('cua.computer', verbosity); + this.logger.info('Initializing Computer...'); + + // Store original parameters + this.image = image; + this.port = port; + this.noVNCPort = noVNCPort; + this.host = host; + this.osType = osType; + this.providerType = providerType; + this.ephemeral = ephemeral; + this.apiKey = apiKey; + this.experiments = experiments; + + if (this.experiments.includes('app-use')) { + if (this.osType !== 'macos') { + throw new Error('App use experiment is only supported on macOS'); + } + } + + // The default is currently to use non-ephemeral storage + if (storage && ephemeral && storage !== 'ephemeral') { + throw new Error('Storage path and ephemeral flag cannot be used together'); + } + this.storage = ephemeral ? 'ephemeral' : storage; + + // For Lumier provider, store the first shared directory path to use + // for VM file sharing + this.sharedPath = undefined; + if (sharedDirectories && sharedDirectories.length > 0) { + this.sharedPath = sharedDirectories[0]; + this.logger.info(`Using first shared directory for VM file sharing: ${this.sharedPath}`); + } + + // Store telemetry preference + this._telemetryEnabled = telemetryEnabled; + + // Configure component loggers with proper hierarchy + this.vmLogger = new Logger('cua.vm', verbosity); + this.interfaceLogger = new Logger('cua.interface', verbosity); + + this.useHostComputerServer = useHostComputerServer; + + if (!useHostComputerServer) { + const imageInfo = parseImageString(image); + + const vmName = name || image.replace(':', '_'); + + // Convert display parameter to Display object + let displayConfig: Display; + if (typeof display === 'string') { + const { width, height } = parseDisplayString(display); + displayConfig = { width, height }; + } else if ('width' in display && 'height' in display) { + displayConfig = display as Display; + } else { + displayConfig = display as Display; + } + + this.config = { + image: imageInfo.name, + tag: imageInfo.tag, + name: vmName, + display: displayConfig, + memory, + cpu, + }; + } + + // Store shared directories config + this.sharedDirectories = sharedDirectories; + + // Record initialization in telemetry (if enabled) + if (telemetryEnabled) { + recordComputerInitialization(); + } else { + this.logger.debug('Telemetry disabled - skipping initialization tracking'); + } + } + + /** + * Create a virtual desktop from a list of app names, returning a DioramaComputer + * that proxies Diorama.Interface but uses diorama_cmds via the computer interface. + * + * @param apps List of application names to include in the desktop. + * @returns A proxy object with the Diorama interface, but using diorama_cmds. + */ + createDesktopFromApps(apps: string[]): any { + if (!this.experiments.includes('app-use')) { + throw new Error("App Usage is an experimental feature. Enable it by passing experiments=['app-use'] to Computer()"); + } + // DioramaComputer would be imported and used here + throw new Error('DioramaComputer not yet implemented'); + } + + /** + * Start the computer (async context manager enter). + */ + async __aenter__(): Promise { + await this.run(); + return this; + } + + /** + * Stop the computer (async context manager exit). + */ + async __aexit__(excType: any, excVal: any, excTb: any): Promise { + await this.disconnect(); + } + + /** + * Initialize the VM and computer interface. + */ + async run(): Promise { + // If already initialized, just log and return + if (this._initialized) { + this.logger.info('Computer already initialized, skipping initialization'); + return; + } + + this.logger.info('Starting computer...'); + const startTime = Date.now(); + + try { + let ipAddress: string; + + // If using host computer server + if (this.useHostComputerServer) { + this.logger.info('Using host computer server'); + ipAddress = 'localhost'; + + // Create the interface + this._interface = InterfaceFactory.createInterfaceForOS( + this.osType, + ipAddress + ); + + this.logger.info('Waiting for host computer server to be ready...'); + await this._interface.waitForReady(); + this.logger.info('Host computer server ready'); + } else { + // Start or connect to VM + this.logger.info(`Starting VM: ${this.image}`); + + if (!this._providerContext) { + try { + const providerTypeName = typeof this.providerType === 'object' + ? this.providerType + : this.providerType; + + this.logger.verbose(`Initializing ${providerTypeName} provider context...`); + + // Create VM provider instance with explicit parameters + const providerOptions = { + port: this.port, + host: this.host, + storage: this.storage, + sharedPath: this.sharedPath, + image: this.image, + verbose: this.verbosity >= LogLevel.DEBUG, + ephemeral: this.ephemeral, + noVNCPort: this.noVNCPort, + apiKey: this.apiKey + }; + + if (!this.config) { + throw new Error('Computer config not initialized'); + } + + this.config.vm_provider = await VMProviderFactory.createProvider( + this.providerType, + providerOptions + ); + + this._providerContext = await this.config.vm_provider.__aenter__(); + this.logger.verbose('VM provider context initialized successfully'); + } catch (error) { + this.logger.error(`Failed to import provider dependencies: ${error}`); + throw error; + } + } + + // Run the VM + if (!this.config || !this.config.vm_provider) { + throw new Error('VM provider not initialized'); + } + + const runOpts = { + display: this.config.display, + memory: this.config.memory, + cpu: this.config.cpu, + shared_directories: this.sharedDirectories + }; + + this.logger.info(`Running VM ${this.config.name} with options:`, runOpts); + + if (this._telemetryEnabled) { + recordVMStart(this.config.name, String(this.providerType)); + } + + const storageParam = this.ephemeral ? 'ephemeral' : this.storage; + + try { + await this.config.vm_provider.runVM( + this.image, + this.config.name, + runOpts, + storageParam + ); + } catch (error: any) { + if (error.message?.includes('already running')) { + this.logger.info(`VM ${this.config.name} is already running`); + } else { + throw error; + } + } + + // Wait for VM to be ready + try { + this.logger.info('Waiting for VM to be ready...'); + await this.waitVMReady(); + + // Get IP address + ipAddress = await this.getIP(); + this.logger.info(`VM is ready with IP: ${ipAddress}`); + } catch (error) { + this.logger.error(`Error waiting for VM: ${error}`); + throw new Error(`VM failed to become ready: ${error}`); + } + } + + // Initialize the interface + try { + // Verify we have a valid IP before initializing the interface + if (!ipAddress || ipAddress === 'unknown' || ipAddress === '0.0.0.0') { + throw new Error(`Cannot initialize interface - invalid IP address: ${ipAddress}`); + } + + this.logger.info(`Initializing interface for ${this.osType} at ${ipAddress}`); + + // Pass authentication credentials if using cloud provider + if (this.providerType === VMProviderType.CLOUD && this.apiKey && this.config?.name) { + this._interface = InterfaceFactory.createInterfaceForOS( + this.osType, + ipAddress, + this.apiKey, + this.config.name + ); + } else { + this._interface = InterfaceFactory.createInterfaceForOS( + this.osType, + ipAddress + ); + } + + // Wait for the WebSocket interface to be ready + this.logger.info('Connecting to WebSocket interface...'); + + try { + await withTimeout( + this._interface.waitForReady(), + 30000, + `Could not connect to WebSocket interface at ${ipAddress}:8000/ws` + ); + this.logger.info('WebSocket interface connected successfully'); + } catch (error) { + this.logger.error(`Failed to connect to WebSocket interface at ${ipAddress}`); + throw error; + } + + // Create an event to keep the VM running in background if needed + if (!this.useHostComputerServer) { + // In TypeScript, we'll use a Promise instead of asyncio.Event + let resolveStop: () => void; + this._stopEvent = new Promise(resolve => { + resolveStop = resolve; + }); + this._keepAliveTask = this._stopEvent; + } + + this.logger.info('Computer is ready'); + + // Set the initialization flag + this._initialized = true; + + // Set this instance as the default computer for remote decorators + setDefaultComputer(this); + + this.logger.info('Computer successfully initialized'); + } catch (error) { + throw error; + } finally { + // Log initialization time for performance monitoring + const durationMs = Date.now() - startTime; + this.logger.debug(`Computer initialization took ${durationMs.toFixed(2)}ms`); + } + } catch (error) { + this.logger.error(`Failed to initialize computer: ${error}`); + throw new Error(`Failed to initialize computer: ${error}`); + } + + return; + } + + /** + * Disconnect from the computer's WebSocket interface. + */ + async disconnect(): Promise { + if (this._interface) { + // Note: The interface close method would need to be implemented + // this._interface.close(); + } + } + + /** + * Disconnect from the computer's WebSocket interface and stop the computer. + */ + async stop(): Promise { + const startTime = Date.now(); + + try { + this.logger.info('Stopping Computer...'); + + // In VM mode, first explicitly stop the VM, then exit the provider context + if (!this.useHostComputerServer && this._providerContext && this.config?.vm_provider) { + try { + this.logger.info(`Stopping VM ${this.config.name}...`); + await this.config.vm_provider.stopVM( + this.config.name, + this.storage + ); + } catch (error) { + this.logger.error(`Error stopping VM: ${error}`); + } + + this.logger.verbose('Closing VM provider context...'); + await this.config.vm_provider.__aexit__(null, null, null); + this._providerContext = undefined; + } + + await this.disconnect(); + this.logger.info('Computer stopped'); + } catch (error) { + this.logger.debug(`Error during cleanup: ${error}`); + } finally { + // Log stop time for performance monitoring + const durationMs = Date.now() - startTime; + this.logger.debug(`Computer stop process took ${durationMs.toFixed(2)}ms`); + + if (this._telemetryEnabled && this.config?.name) { + recordVMStop(this.config.name, durationMs); + } + } + } + + /** + * Get the IP address of the VM or localhost if using host computer server. + */ + async getIP(maxRetries: number = 15, retryDelay: number = 2): Promise { + // For host computer server, always return localhost immediately + if (this.useHostComputerServer) { + return '127.0.0.1'; + } + + // Get IP from the provider + if (!this.config?.vm_provider) { + throw new Error('VM provider is not initialized'); + } + + // Log that we're waiting for the IP + this.logger.info(`Waiting for VM ${this.config.name} to get an IP address...`); + + // Call the provider's get_ip method which will wait indefinitely + const storageParam = this.ephemeral ? 'ephemeral' : this.storage; + + // Log the image being used + this.logger.info(`Running VM using image: ${this.image}`); + + // Call provider.getIP with explicit parameters + const ip = await this.config.vm_provider.getIP( + this.config.name, + storageParam, + retryDelay + ); + + // Log success + this.logger.info(`VM ${this.config.name} has IP address: ${ip}`); + return ip; + } + + /** + * Wait for VM to be ready with an IP address. + */ + async waitVMReady(): Promise | undefined> { + if (this.useHostComputerServer) { + return undefined; + } + + const timeout = 600; // 10 minutes timeout + const interval = 2.0; // 2 seconds between checks + const startTime = Date.now() / 1000; + let lastStatus: string | undefined; + let attempts = 0; + + this.logger.info(`Waiting for VM ${this.config?.name} to be ready (timeout: ${timeout}s)...`); + + while ((Date.now() / 1000) - startTime < timeout) { + attempts++; + const elapsed = (Date.now() / 1000) - startTime; + + try { + // Keep polling for VM info + if (!this.config?.vm_provider) { + this.logger.error('VM provider is not initialized'); + return undefined; + } + + const vm = await this.config.vm_provider.getVM(this.config.name); + + // Log full VM properties for debugging (every 30 attempts) + if (attempts % 30 === 0) { + this.logger.info( + `VM properties at attempt ${attempts}: ${JSON.stringify(vm)}` + ); + } + + // Get current status for logging + const currentStatus = vm?.status; + if (currentStatus !== lastStatus) { + this.logger.info( + `VM status changed to: ${currentStatus} (after ${elapsed.toFixed(1)}s)` + ); + lastStatus = currentStatus; + } + + // Check if VM is ready + if (vm && vm.status === 'running' && vm.ip_address && vm.ip_address !== '0.0.0.0') { + this.logger.info(`VM ${this.config.name} is ready with IP: ${vm.ip_address}`); + return vm; + } + + // Wait before next check + await sleep(interval * 1000); + } catch (error) { + this.logger.error(`Error checking VM status: ${error}`); + await sleep(interval * 1000); + } + } + + throw new Error(`VM ${this.config?.name} failed to become ready within ${timeout} seconds`); + } + + /** + * Update VM settings. + */ + async update(cpu?: number, memory?: string): Promise { + if (this.useHostComputerServer) { + this.logger.warning('Cannot update settings for host computer server'); + return; + } + + if (!this.config?.vm_provider) { + throw new Error('VM provider is not initialized'); + } + + await this.config.vm_provider.updateVM( + this.config.name, + cpu, + memory, + this.storage + ); + } + + /** + * Get the dimensions of a screenshot. + */ + async getScreenshotSize(screenshot: Buffer): Promise<{ width: number; height: number }> { + const metadata = await sharp(screenshot).metadata(); + return { + width: metadata.width || 0, + height: metadata.height || 0 + }; + } + + /** + * Get the computer interface for interacting with the VM. + */ + get interface(): BaseComputerInterface { + if (!this._interface) { + throw new Error('Computer interface not initialized. Call run() first.'); + } + return this._interface; + } + + /** + * Check if telemetry is enabled for this computer instance. + */ + get telemetryEnabled(): boolean { + return this._telemetryEnabled; + } + + /** + * Convert normalized coordinates to screen coordinates. + */ + toScreenCoordinates(x: number, y: number): [number, number] { + if (!this.config?.display) { + throw new Error('Display configuration not available'); + } + return [ + x * this.config.display.width, + y * this.config.display.height + ]; + } + + /** + * Convert screen coordinates to screenshot coordinates. + */ + async toScreenshotCoordinates(x: number, y: number): Promise<[number, number]> { + // In the Python version, this uses the interface to get screenshot dimensions + // For now, we'll assume 1:1 mapping + return [x, y]; + } + + /** + * Install packages in a virtual environment. + */ + async venvInstall(venvName: string, requirements: string[]): Promise<[string, string]> { + // This would be implemented using the interface to run commands + // TODO: Implement venvInstall + throw new Error('venvInstall not yet implemented'); + } + + /** + * Execute a shell command in a virtual environment. + */ + async venvCmd(venvName: string, command: string): Promise<[string, string]> { + // This would be implemented using the interface to run commands + // TODO: Implement venvCmd + throw new Error('venvCmd not yet implemented'); + } + + /** + * Execute function in a virtual environment using source code extraction. + */ + async venvExec(venvName: string, pythonFunc: Function, ...args: any[]): Promise { + // This would be implemented using the interface to run Python code + // TODO: Implement venvExec + throw new Error('venvExec not yet implemented'); + } +} diff --git a/libs/computer/typescript/src/computer/index.ts b/libs/computer/typescript/src/computer/index.ts new file mode 100644 index 00000000..706e2bda --- /dev/null +++ b/libs/computer/typescript/src/computer/index.ts @@ -0,0 +1,2 @@ +// Re-export the Computer class and related types +export * from './computer'; diff --git a/libs/computer/typescript/src/helpers.ts b/libs/computer/typescript/src/helpers.ts new file mode 100644 index 00000000..44da61f8 --- /dev/null +++ b/libs/computer/typescript/src/helpers.ts @@ -0,0 +1,76 @@ +/** + * Helper functions and decorators for the Computer module. + */ + +import type { Computer } from './computer'; + +// Global reference to the default computer instance +let _defaultComputer: Computer | null = null; + +/** + * Set the default computer instance to be used by the remote decorator. + * + * @param computer The computer instance to use as default + */ +export function setDefaultComputer(computer: Computer): void { + _defaultComputer = computer; +} + +/** + * Get the default computer instance. + * + * @returns The default computer instance or null + */ +export function getDefaultComputer(): Computer | null { + return _defaultComputer; +} + +/** + * Decorator that wraps a function to be executed remotely via computer.venvExec + * + * @param venvName Name of the virtual environment to execute in + * @param computer The computer instance to use, or "default" to use the globally set default + * @param maxRetries Maximum number of retries for the remote execution + */ +export function sandboxed( + venvName: string = 'default', + computer: Computer | 'default' = 'default', + maxRetries: number = 3 +) { + return function any>( + target: any, + propertyKey: string, + descriptor: PropertyDescriptor + ) { + const originalMethod = descriptor.value; + + descriptor.value = async function (...args: Parameters): Promise> { + // Determine which computer instance to use + const comp = computer === 'default' ? _defaultComputer : computer; + + if (!comp) { + throw new Error( + 'No computer instance available. Either specify a computer instance or call setDefaultComputer() first.' + ); + } + + for (let i = 0; i < maxRetries; i++) { + try { + return await comp.venvExec(venvName, originalMethod, ...args); + } catch (error) { + console.error(`Attempt ${i + 1} failed:`, error); + if (i < maxRetries - 1) { + await new Promise(resolve => setTimeout(resolve, 1000)); + } else { + throw error; + } + } + } + + // This should never be reached, but satisfies TypeScript's control flow analysis + throw new Error('Unexpected: maxRetries loop completed without returning or throwing'); + }; + + return descriptor; + }; +} diff --git a/libs/computer/typescript/src/index.ts b/libs/computer/typescript/src/index.ts new file mode 100644 index 00000000..551181ca --- /dev/null +++ b/libs/computer/typescript/src/index.ts @@ -0,0 +1,28 @@ +// Core components +export { Computer } from "./computer"; +export type { ComputerOptions, OSType } from "./computer"; + +// Models +export type { Display, ComputerConfig } from "./models"; + +// Provider components +export { VMProviderType, BaseVMProviderImpl } from "./providers"; +export type { BaseVMProvider } from "./providers"; +export { VMProviderFactory } from "./providers"; +export type { VMProviderOptions } from "./providers"; + +// Interface components +export type { BaseComputerInterface } from "./interface"; +export { InterfaceFactory } from "./interface"; +export type { InterfaceOptions } from "./interface"; +export { Key } from "./interface"; +export type { + KeyType, + MouseButton, + NavigationKey, + SpecialKey, + ModifierKey, + FunctionKey, + AccessibilityWindow, + AccessibilityTree +} from "./interface"; diff --git a/libs/computer/typescript/src/interface/base.ts b/libs/computer/typescript/src/interface/base.ts new file mode 100644 index 00000000..ef4dfe6c --- /dev/null +++ b/libs/computer/typescript/src/interface/base.ts @@ -0,0 +1,41 @@ +import type { KeyType, MouseButton, AccessibilityTree } from './models'; + +/** + * Base interface for computer control implementations. + */ +export interface BaseComputerInterface { + /** + * Wait for the interface to be ready. + */ + waitForReady(): Promise; + + /** + * Get a screenshot of the current screen. + */ + getScreenshot(): Promise; + + /** + * Move the mouse to the specified coordinates. + */ + moveMouse(x: number, y: number): Promise; + + /** + * Click the mouse at the current position. + */ + click(button?: MouseButton): Promise; + + /** + * Type text at the current cursor position. + */ + typeText(text: string): Promise; + + /** + * Press a key. + */ + pressKey(key: KeyType): Promise; + + /** + * Get the accessibility tree. + */ + getAccessibilityTree(): Promise; +} diff --git a/libs/computer/typescript/src/interface/factory.ts b/libs/computer/typescript/src/interface/factory.ts new file mode 100644 index 00000000..8f3fffba --- /dev/null +++ b/libs/computer/typescript/src/interface/factory.ts @@ -0,0 +1,56 @@ +import type { BaseComputerInterface } from './base'; +import type { OSType } from '../computer'; + +export interface InterfaceOptions { + ipAddress: string; + apiKey?: string; + vmName?: string; +} + +/** + * Factory for creating OS-specific computer interfaces. + */ +export class InterfaceFactory { + /** + * Create an interface for the specified OS. + * + * @param os The operating system type ('macos', 'linux', 'windows') + * @param ipAddress The IP address to connect to + * @param apiKey Optional API key for cloud providers + * @param vmName Optional VM name for cloud providers + * @returns An instance of the appropriate computer interface + */ + static createInterfaceForOS( + os: OSType, + ipAddress: string, + apiKey?: string, + vmName?: string + ): BaseComputerInterface { + const options: InterfaceOptions = { + ipAddress, + apiKey, + vmName + }; + + switch (os) { + case 'macos': + // Dynamic import would be used in real implementation + // TODO: Implement macOS interface + throw new Error('macOS interface not yet implemented'); + + case 'linux': + // Dynamic import would be used in real implementation + // TODO: Implement Linux interface + throw new Error('Linux interface not yet implemented'); + + case 'windows': + // Dynamic import would be used in real implementation + // TODO: Implement Windows interface + throw new Error('Windows interface not yet implemented'); + + default: + // TODO: Implement interface for this OS + throw new Error(`Interface for OS ${os} not implemented`); + } + } +} diff --git a/libs/computer/typescript/src/interface/index.ts b/libs/computer/typescript/src/interface/index.ts new file mode 100644 index 00000000..ac96f84e --- /dev/null +++ b/libs/computer/typescript/src/interface/index.ts @@ -0,0 +1,5 @@ +export * from "./base"; +export * from "./factory"; +export * from "./models"; +export * from "./macos"; +export * from "./linux"; diff --git a/libs/computer/typescript/src/interface/linux.ts b/libs/computer/typescript/src/interface/linux.ts new file mode 100644 index 00000000..3713cb7b --- /dev/null +++ b/libs/computer/typescript/src/interface/linux.ts @@ -0,0 +1,47 @@ +import type { BaseComputerInterface } from './base'; +import type { KeyType, MouseButton, AccessibilityTree } from './models'; + +/** + * Linux-specific implementation of the computer interface. + */ +export class LinuxComputerInterface implements BaseComputerInterface { + private ip_address: string; + + constructor(ip_address: string) { + this.ip_address = ip_address; + } + + async waitForReady(): Promise { + // Implementation will go here + } + + async getScreenshot(): Promise { + // Implementation will go here + return Buffer.from([]); + } + + async moveMouse(x: number, y: number): Promise { + // Implementation will go here + } + + async click(button: MouseButton = 'left'): Promise { + // Implementation will go here + } + + async typeText(text: string): Promise { + // Implementation will go here + } + + async pressKey(key: KeyType): Promise { + // Implementation will go here + } + + async getAccessibilityTree(): Promise { + // Implementation will go here + return { + success: false, + frontmost_application: '', + windows: [] + }; + } +} diff --git a/libs/computer/typescript/src/interface/macos.ts b/libs/computer/typescript/src/interface/macos.ts new file mode 100644 index 00000000..a031fa49 --- /dev/null +++ b/libs/computer/typescript/src/interface/macos.ts @@ -0,0 +1,47 @@ +import type { BaseComputerInterface } from './base'; +import type { KeyType, MouseButton, AccessibilityTree } from './models'; + +/** + * macOS-specific implementation of the computer interface. + */ +export class MacOSComputerInterface implements BaseComputerInterface { + private ip_address: string; + + constructor(ip_address: string) { + this.ip_address = ip_address; + } + + async waitForReady(): Promise { + // Implementation will go here + } + + async getScreenshot(): Promise { + // Implementation will go here + return Buffer.from([]); + } + + async moveMouse(x: number, y: number): Promise { + // Implementation will go here + } + + async click(button: MouseButton = 'left'): Promise { + // Implementation will go here + } + + async typeText(text: string): Promise { + // Implementation will go here + } + + async pressKey(key: KeyType): Promise { + // Implementation will go here + } + + async getAccessibilityTree(): Promise { + // Implementation will go here + return { + success: false, + frontmost_application: '', + windows: [] + }; + } +} diff --git a/libs/computer/typescript/src/interface/models.ts b/libs/computer/typescript/src/interface/models.ts new file mode 100644 index 00000000..80f2b0a7 --- /dev/null +++ b/libs/computer/typescript/src/interface/models.ts @@ -0,0 +1,96 @@ +/** + * Navigation key literals + */ +export type NavigationKey = 'pagedown' | 'pageup' | 'home' | 'end' | 'left' | 'right' | 'up' | 'down'; + +/** + * Special key literals + */ +export type SpecialKey = 'enter' | 'esc' | 'tab' | 'space' | 'backspace' | 'del'; + +/** + * Modifier key literals + */ +export type ModifierKey = 'ctrl' | 'alt' | 'shift' | 'win' | 'command' | 'option'; + +/** + * Function key literals + */ +export type FunctionKey = 'f1' | 'f2' | 'f3' | 'f4' | 'f5' | 'f6' | 'f7' | 'f8' | 'f9' | 'f10' | 'f11' | 'f12'; + +/** + * Keyboard keys that can be used with press_key. + */ +export enum Key { + // Navigation + PAGE_DOWN = 'pagedown', + PAGE_UP = 'pageup', + HOME = 'home', + END = 'end', + LEFT = 'left', + RIGHT = 'right', + UP = 'up', + DOWN = 'down', + + // Special keys + RETURN = 'enter', + ENTER = 'enter', + ESCAPE = 'esc', + ESC = 'esc', + TAB = 'tab', + SPACE = 'space', + BACKSPACE = 'backspace', + DELETE = 'del', + + // Modifier keys + ALT = 'alt', + CTRL = 'ctrl', + SHIFT = 'shift', + WIN = 'win', + COMMAND = 'command', + OPTION = 'option', + + // Function keys + F1 = 'f1', + F2 = 'f2', + F3 = 'f3', + F4 = 'f4', + F5 = 'f5', + F6 = 'f6', + F7 = 'f7', + F8 = 'f8', + F9 = 'f9', + F10 = 'f10', + F11 = 'f11', + F12 = 'f12' +} + +/** + * Combined key type + */ +export type KeyType = Key | NavigationKey | SpecialKey | ModifierKey | FunctionKey | string; + +/** + * Key type for mouse actions + */ +export type MouseButton = 'left' | 'right' | 'middle'; + +/** + * Information about a window in the accessibility tree. + */ +export interface AccessibilityWindow { + app_name: string; + pid: number; + frontmost: boolean; + has_windows: boolean; + windows: Array>; +} + +/** + * Complete accessibility tree information. + */ +export interface AccessibilityTree { + success: boolean; + frontmost_application: string; + windows: AccessibilityWindow[]; +} diff --git a/libs/computer/typescript/src/logger.ts b/libs/computer/typescript/src/logger.ts new file mode 100644 index 00000000..83f74425 --- /dev/null +++ b/libs/computer/typescript/src/logger.ts @@ -0,0 +1,50 @@ +/** + * Logger implementation for the Computer library. + */ + +export enum LogLevel { + DEBUG = 10, + VERBOSE = 15, + INFO = 20, + NORMAL = 20, + WARNING = 30, + ERROR = 40, +} + +export class Logger { + private name: string; + private verbosity: number; + + constructor(name: string, verbosity: number | LogLevel = LogLevel.NORMAL) { + this.name = name; + this.verbosity = typeof verbosity === 'number' ? verbosity : verbosity; + } + + private log(level: LogLevel, message: string, ...args: any[]): void { + if (level >= this.verbosity) { + const timestamp = new Date().toISOString(); + const levelName = LogLevel[level]; + console.log(`[${timestamp}] [${this.name}] [${levelName}] ${message}`, ...args); + } + } + + debug(message: string, ...args: any[]): void { + this.log(LogLevel.DEBUG, message, ...args); + } + + info(message: string, ...args: any[]): void { + this.log(LogLevel.INFO, message, ...args); + } + + verbose(message: string, ...args: any[]): void { + this.log(LogLevel.VERBOSE, message, ...args); + } + + warning(message: string, ...args: any[]): void { + this.log(LogLevel.WARNING, message, ...args); + } + + error(message: string, ...args: any[]): void { + this.log(LogLevel.ERROR, message, ...args); + } +} diff --git a/libs/computer/typescript/src/models.ts b/libs/computer/typescript/src/models.ts new file mode 100644 index 00000000..b0749cc5 --- /dev/null +++ b/libs/computer/typescript/src/models.ts @@ -0,0 +1,21 @@ +/** + * Display configuration for the computer. + */ +export interface Display { + width: number; + height: number; + scale_factor?: number; +} + +/** + * Computer configuration model. + */ +export interface ComputerConfig { + image: string; + tag: string; + name: string; + display: Display; + memory: string; + cpu: string; + vm_provider?: any; // Will be properly typed when implemented +} diff --git a/libs/computer/typescript/src/providers/base.ts b/libs/computer/typescript/src/providers/base.ts new file mode 100644 index 00000000..d5368a93 --- /dev/null +++ b/libs/computer/typescript/src/providers/base.ts @@ -0,0 +1,140 @@ +/** + * Types of VM providers available. + */ +export enum VMProviderType { + LUME = 'lume', + LUMIER = 'lumier', + CLOUD = 'cloud', + UNKNOWN = 'unknown' +} + +/** + * Base interface for VM providers. + * All VM provider implementations must implement this interface. + */ +export interface BaseVMProvider { + /** + * Get the provider type. + */ + readonly providerType: VMProviderType; + + /** + * Get VM information by name. + * + * @param name Name of the VM to get information for + * @param storage Optional storage path override + * @returns Dictionary with VM information including status, IP address, etc. + */ + getVM(name: string, storage?: string): Promise>; + + /** + * List all available VMs. + */ + listVMs(): Promise>>; + + /** + * Run a VM by name with the given options. + * + * @param image VM image to run + * @param name Name for the VM + * @param runOpts Run options for the VM + * @param storage Optional storage path + * @returns VM run response + */ + runVM( + image: string, + name: string, + runOpts: Record, + storage?: string + ): Promise>; + + /** + * Stop a VM by name. + * + * @param name Name of the VM to stop + * @param storage Optional storage path + */ + stopVM(name: string, storage?: string): Promise; + + /** + * Get the IP address of a VM. + * + * @param name Name of the VM + * @param storage Optional storage path + * @param retryDelay Delay between retries in seconds + * @returns IP address of the VM + */ + getIP(name: string, storage?: string, retryDelay?: number): Promise; + + /** + * Update VM settings. + * + * @param name Name of the VM + * @param cpu New CPU allocation + * @param memory New memory allocation + * @param storage Optional storage path + */ + updateVM( + name: string, + cpu?: number, + memory?: string, + storage?: string + ): Promise; + + /** + * Context manager enter method + */ + __aenter__(): Promise; + + /** + * Context manager exit method + */ + __aexit__( + excType: any, + excVal: any, + excTb: any + ): Promise; +} + +/** + * Abstract base class for VM providers that implements context manager + */ +export abstract class BaseVMProviderImpl implements BaseVMProvider { + abstract readonly providerType: VMProviderType; + + abstract getVM(name: string, storage?: string): Promise>; + abstract listVMs(): Promise>>; + abstract runVM( + image: string, + name: string, + runOpts: Record, + storage?: string + ): Promise>; + abstract stopVM(name: string, storage?: string): Promise; + abstract getIP(name: string, storage?: string, retryDelay?: number): Promise; + abstract updateVM( + name: string, + cpu?: number, + memory?: string, + storage?: string + ): Promise; + + async __aenter__(): Promise { + // Default implementation - can be overridden + return this; + } + + /** + * Async context manager exit. + * + * This method is called when exiting an async context manager block. + * It handles proper cleanup of resources, including stopping any running containers. + */ + async __aexit__( + _excType: any, + _excVal: any, + _excTb: any + ): Promise { + // Default implementation - can be overridden + } +} diff --git a/libs/computer/typescript/src/providers/cloud/index.ts b/libs/computer/typescript/src/providers/cloud/index.ts new file mode 100644 index 00000000..6b434c36 --- /dev/null +++ b/libs/computer/typescript/src/providers/cloud/index.ts @@ -0,0 +1,5 @@ +/** + * Cloud VM provider implementation. + */ + +export { CloudProvider } from "./provider"; diff --git a/libs/computer/typescript/src/providers/cloud/provider.ts b/libs/computer/typescript/src/providers/cloud/provider.ts new file mode 100644 index 00000000..573f699c --- /dev/null +++ b/libs/computer/typescript/src/providers/cloud/provider.ts @@ -0,0 +1,68 @@ +/** + * Cloud VM provider implementation. + * + * This provider is a placeholder for cloud-based VM provisioning. + * It will be implemented to support cloud VM services in the future. + */ + +import { BaseVMProviderImpl, VMProviderType } from '../base'; + +export interface CloudProviderOptions { + verbose?: boolean; + apiKey?: string; + region?: string; + [key: string]: any; +} + +export class CloudProvider extends BaseVMProviderImpl { + readonly providerType = VMProviderType.CLOUD; + + private verbose: boolean; + private options: CloudProviderOptions; + + constructor(options: CloudProviderOptions = {}) { + super(); + this.verbose = options.verbose || false; + this.options = options; + } + + // TODO: Implement getVM for cloud provider + async getVM(name: string, storage?: string): Promise> { + throw new Error('CloudProvider is not fully implemented yet. Please use LUME or LUMIER provider instead.'); + } + + // TODO: Implement listVMs for cloud provider + async listVMs(): Promise>> { + throw new Error('CloudProvider is not fully implemented yet. Please use LUME or LUMIER provider instead.'); + } + + // TODO: Implement runVM for cloud provider + async runVM( + image: string, + name: string, + runOpts: Record, + storage?: string + ): Promise> { + throw new Error('CloudProvider is not fully implemented yet. Please use LUME or LUMIER provider instead.'); + } + + // TODO: Implement stopVM for cloud provider + async stopVM(name: string, storage?: string): Promise { + throw new Error('CloudProvider is not fully implemented yet. Please use LUME or LUMIER provider instead.'); + } + + // TODO: Implement getIP for cloud provider + async getIP(name: string, storage?: string, retryDelay?: number): Promise { + throw new Error('CloudProvider is not fully implemented yet. Please use LUME or LUMIER provider instead.'); + } + + // TODO: Implement updateVM for cloud provider + async updateVM( + name: string, + cpu?: number, + memory?: string, + storage?: string + ): Promise { + throw new Error('CloudProvider is not fully implemented yet. Please use LUME or LUMIER provider instead.'); + } +} diff --git a/libs/computer/typescript/src/providers/factory.ts b/libs/computer/typescript/src/providers/factory.ts new file mode 100644 index 00000000..a2efb149 --- /dev/null +++ b/libs/computer/typescript/src/providers/factory.ts @@ -0,0 +1,150 @@ +/** + * Factory for creating VM providers. + */ + +import type { BaseVMProvider } from './base'; +import { VMProviderType } from './base'; + +export interface VMProviderOptions { + port?: number; + host?: string; + binPath?: string; + storage?: string; + sharedPath?: string; + image?: string; + verbose?: boolean; + ephemeral?: boolean; + noVNCPort?: number; + [key: string]: any; // Allow additional provider-specific options +} + +export class VMProviderFactory { + /** + * Create a VM provider instance based on the provider type. + * + * @param providerType The type of provider to create + * @param options Provider-specific options + * @returns The created VM provider instance + * @throws Error if the provider type is not supported or dependencies are missing + */ + static async createProvider( + providerType: VMProviderType | string, + options: VMProviderOptions = {} + ): Promise { + // Convert string to enum if needed + let type: VMProviderType; + if (typeof providerType === 'string') { + const normalizedType = providerType.toLowerCase(); + if (Object.values(VMProviderType).includes(normalizedType as VMProviderType)) { + type = normalizedType as VMProviderType; + } else { + type = VMProviderType.UNKNOWN; + } + } else { + type = providerType; + } + + // Extract common options with defaults + const { + port = 7777, + host = 'localhost', + binPath, + storage, + sharedPath, + image, + verbose = false, + ephemeral = false, + noVNCPort, + ...additionalOptions + } = options; + + switch (type) { + case VMProviderType.LUME: { + try { + // Dynamic import for Lume provider + const { LumeProvider, HAS_LUME } = await import('./lume'); + + if (!HAS_LUME) { + throw new Error( + 'The required dependencies for LumeProvider are not available. ' + + 'Please ensure curl is installed and in your PATH.' + ); + } + + return new LumeProvider({ + port, + host, + storage, + verbose, + ephemeral + }); + } catch (error) { + if (error instanceof Error && error.message.includes('Cannot find module')) { + throw new Error( + 'The LumeProvider module is not available. ' + + 'Please install it with: npm install @cua/computer-lume' + ); + } + throw error; + } + } + + case VMProviderType.LUMIER: { + try { + // Dynamic import for Lumier provider + const { LumierProvider, HAS_LUMIER } = await import('./lumier'); + + if (!HAS_LUMIER) { + throw new Error( + 'Docker is required for LumierProvider. ' + + 'Please install Docker for Apple Silicon and Lume CLI before using this provider.' + ); + } + + return new LumierProvider({ + port, + host, + storage, + sharedPath, + image: image || 'macos-sequoia-cua:latest', + verbose, + ephemeral, + noVNCPort + }); + } catch (error) { + if (error instanceof Error && error.message.includes('Cannot find module')) { + throw new Error( + 'The LumierProvider module is not available. ' + + 'Docker and Lume CLI are required for LumierProvider. ' + + 'Please install Docker for Apple Silicon and run the Lume installer script.' + ); + } + throw error; + } + } + + case VMProviderType.CLOUD: { + try { + // Dynamic import for Cloud provider + const { CloudProvider } = await import('./cloud'); + + return new CloudProvider({ + verbose, + ...additionalOptions + }); + } catch (error) { + if (error instanceof Error && error.message.includes('Cannot find module')) { + throw new Error( + 'The CloudProvider is not fully implemented yet. ' + + 'Please use LUME or LUMIER provider instead.' + ); + } + throw error; + } + } + + default: + throw new Error(`Unsupported provider type: ${providerType}`); + } + } +} diff --git a/libs/computer/typescript/src/providers/index.ts b/libs/computer/typescript/src/providers/index.ts new file mode 100644 index 00000000..078b4cbd --- /dev/null +++ b/libs/computer/typescript/src/providers/index.ts @@ -0,0 +1,12 @@ +/** + * Export all provider-related modules. + */ + +export * from './base'; +export * from './factory'; +export * from './lume_api'; + +// Export provider implementations +export * from './lume'; +export * from './lumier'; +export * from './cloud'; diff --git a/libs/computer/typescript/src/providers/lume/index.ts b/libs/computer/typescript/src/providers/lume/index.ts new file mode 100644 index 00000000..9b020d8e --- /dev/null +++ b/libs/computer/typescript/src/providers/lume/index.ts @@ -0,0 +1,16 @@ +/** + * Lume VM provider implementation. + */ + +export let HAS_LUME = false; + +try { + // Check if curl is available + const { execSync } = require('child_process'); + execSync('which curl', { stdio: 'ignore' }); + HAS_LUME = true; +} catch { + HAS_LUME = false; +} + +export { LumeProvider } from './provider'; diff --git a/libs/computer/typescript/src/providers/lume/provider.ts b/libs/computer/typescript/src/providers/lume/provider.ts new file mode 100644 index 00000000..7c8c980a --- /dev/null +++ b/libs/computer/typescript/src/providers/lume/provider.ts @@ -0,0 +1,182 @@ +/** + * Lume VM provider implementation using curl commands. + * + * This provider uses direct curl commands to interact with the Lume API, + * removing the dependency on the pylume Python package. + */ + +import { BaseVMProviderImpl, VMProviderType } from '../base'; +import type { LumeRunOptions } from '../lume_api'; +import { + HAS_CURL, + lumeApiGet, + lumeApiRun, + lumeApiStop, + lumeApiUpdate, + lumeApiPull, + parseMemory +} from '../lume_api'; + +export interface LumeProviderOptions { + port?: number; + host?: string; + storage?: string; + verbose?: boolean; + ephemeral?: boolean; +} + +export class LumeProvider extends BaseVMProviderImpl { + readonly providerType = VMProviderType.LUME; + + private host: string; + private port: number; + private storage?: string; + private verbose: boolean; + private ephemeral: boolean; + + constructor(options: LumeProviderOptions = {}) { + super(); + + if (!HAS_CURL) { + throw new Error( + 'curl is required for LumeProvider. ' + + 'Please ensure it is installed and in your PATH.' + ); + } + + this.host = options.host || 'localhost'; + this.port = options.port || 7777; + this.storage = options.storage; + this.verbose = options.verbose || false; + this.ephemeral = options.ephemeral || false; + } + + async getVM(name: string, storage?: string): Promise> { + return lumeApiGet( + name, + storage || this.storage, + this.host, + this.port, + this.verbose + ); + } + + async listVMs(): Promise>> { + const response = await lumeApiGet( + '', + this.storage, + this.host, + this.port, + this.verbose + ); + + // The response should be an array of VMs + if (Array.isArray(response)) { + return response; + } + + // If it's an object with a vms property + if (response.vms && Array.isArray(response.vms)) { + return response.vms; + } + + // Otherwise return empty array + return []; + } + + async runVM( + image: string, + name: string, + runOpts: LumeRunOptions, + storage?: string + ): Promise> { + // Ensure the image is available + if (this.verbose) { + console.log(`Pulling image ${image} if needed...`); + } + + try { + await lumeApiPull(image, this.host, this.port, this.verbose); + } catch (error) { + if (this.verbose) { + console.log(`Failed to pull image: ${error}`); + } + } + + // Run the VM + return lumeApiRun( + image, + name, + runOpts, + storage || this.storage, + this.host, + this.port, + this.verbose + ); + } + + async stopVM(name: string, storage?: string): Promise { + await lumeApiStop( + name, + storage || this.storage, + this.host, + this.port, + this.verbose + ); + + // If ephemeral, the VM should be automatically deleted after stopping + if (this.ephemeral && this.verbose) { + console.log(`VM ${name} stopped and removed (ephemeral mode)`); + } + } + + async getIP(name: string, storage?: string, retryDelay: number = 1): Promise { + const maxRetries = 30; + let retries = 0; + + while (retries < maxRetries) { + try { + const vmInfo = await this.getVM(name, storage); + + if (vmInfo.ip && vmInfo.ip !== '') { + return vmInfo.ip; + } + + if (vmInfo.status === 'stopped' || vmInfo.status === 'error') { + throw new Error(`VM ${name} is in ${vmInfo.status} state`); + } + } catch (error) { + if (retries === maxRetries - 1) { + throw error; + } + } + + retries++; + await new Promise(resolve => setTimeout(resolve, retryDelay * 1000)); + } + + throw new Error(`Failed to get IP for VM ${name} after ${maxRetries} retries`); + } + + async updateVM( + name: string, + cpu?: number, + memory?: string, + storage?: string + ): Promise { + // Validate memory format if provided + if (memory) { + parseMemory(memory); // This will throw if invalid + } + + await lumeApiUpdate( + name, + cpu, + memory, + storage || this.storage, + this.host, + this.port, + this.verbose + ); + } +} diff --git a/libs/computer/typescript/src/providers/lume_api.ts b/libs/computer/typescript/src/providers/lume_api.ts new file mode 100644 index 00000000..c198583d --- /dev/null +++ b/libs/computer/typescript/src/providers/lume_api.ts @@ -0,0 +1,265 @@ +/** + * Lume API utilities for interacting with the Lume VM API. + * This module provides low-level API functions used by the Lume provider. + */ + +import { exec, execSync } from "child_process"; +import { promisify } from "util"; + +const execAsync = promisify(exec); + +export let HAS_CURL = false; + +// Check for curl availability +try { + execSync("which curl", { stdio: "ignore" }); + HAS_CURL = true; +} catch { + HAS_CURL = false; +} + +/** + * Parse memory string to bytes. + * Supports formats like "2GB", "512MB", "1024KB", etc. + * Defaults to 1GB + */ +export function parseMemory(memory = "1GB"): number { + const match = memory.match(/^(\d+(?:\.\d+)?)\s*([KMGT]?B?)$/i); + if (!match) { + throw new Error(`Invalid memory format: ${memory}`); + } + + const value = parseFloat(match[1]!); + const unit = match[2]!.toUpperCase(); + + const multipliers: Record = { + B: 1, + KB: 1024, + MB: 1024 * 1024, + GB: 1024 * 1024 * 1024, + TB: 1024 * 1024 * 1024 * 1024, + K: 1024, + M: 1024 * 1024, + G: 1024 * 1024 * 1024, + T: 1024 * 1024 * 1024 * 1024, + }; + + return Math.floor(value * (multipliers[unit] || 1)); +} + +/** + * Execute a curl command and return the result. + */ +async function executeCurl( + command: string +): Promise<{ stdout: string; stderr: string }> { + try { + const { stdout, stderr } = await execAsync(command); + return { stdout, stderr }; + } catch (error: any) { + throw new Error(`Curl command failed: ${error.message}`); + } +} + +/** + * Get VM information using Lume API. + */ +export async function lumeApiGet( + vmName: string = "", + storage?: string, + host: string = "localhost", + port: number = 7777, + debug: boolean = false +): Promise> { + let url = `http://${host}:${port}/vms`; + if (vmName) { + url += `/${encodeURIComponent(vmName)}`; + } + + const params = new URLSearchParams(); + if (storage) { + params.append("storage", storage); + } + + if (params.toString()) { + url += `?${params.toString()}`; + } + + const command = `curl -s -X GET "${url}"`; + + if (debug) { + console.log(`Executing: ${command}`); + } + + const { stdout } = await executeCurl(command); + + try { + return JSON.parse(stdout); + } catch (error) { + throw new Error(`Failed to parse API response: ${stdout}`); + } +} + +/** + * Options for running a VM using the Lume API. + */ +export interface LumeRunOptions { + /** CPU cores to allocate to the VM */ + cpu?: number; + /** Memory to allocate to the VM (e.g., "8GB", "512MB") */ + memory?: string; + /** Display configuration for the VM */ + display?: { + width?: number; + height?: number; + dpi?: number; + color_depth?: number; + }; + /** Environment variables to set in the VM */ + env?: Record; + /** Directories to share with the VM */ + shared_directories?: Record; + /** Network configuration */ + network?: { + type?: string; + bridge?: string; + nat?: boolean; + }; + /** Whether to run the VM in headless mode */ + headless?: boolean; + /** Whether to enable GPU acceleration */ + gpu?: boolean; + /** Storage location for the VM */ + storage?: string; + /** Custom VM configuration options */ + vm_options?: Record; + /** Additional provider-specific options */ + [key: string]: any; +} + +/** + * Run a VM using Lume API. + */ +export async function lumeApiRun( + image: string, + name: string, + runOpts: LumeRunOptions, + storage?: string, + host: string = "localhost", + port: number = 7777, + debug: boolean = false +): Promise> { + const url = `http://${host}:${port}/vms/run`; + + const body: LumeRunOptions = { + image, + name, + ...runOpts, + }; + + if (storage) { + body.storage = storage; + } + + const command = `curl -s -X POST "${url}" -H "Content-Type: application/json" -d '${JSON.stringify( + body + )}'`; + + if (debug) { + console.log(`Executing: ${command}`); + } + + const { stdout } = await executeCurl(command); + + try { + return JSON.parse(stdout); + } catch (error) { + throw new Error(`Failed to parse API response: ${stdout}`); + } +} + +/** + * Stop a VM using Lume API. + */ +export async function lumeApiStop( + vmName: string, + storage?: string, + host: string = "localhost", + port: number = 7777, + debug: boolean = false +): Promise { + const url = `http://${host}:${port}/vms/${encodeURIComponent(vmName)}/stop`; + + const params = new URLSearchParams(); + if (storage) { + params.append("storage", storage); + } + + const fullUrl = params.toString() ? `${url}?${params.toString()}` : url; + const command = `curl -s -X POST "${fullUrl}"`; + + if (debug) { + console.log(`Executing: ${command}`); + } + + await executeCurl(command); +} + +/** + * Update VM settings using Lume API. + */ +export async function lumeApiUpdate( + vmName: string, + cpu?: number, + memory?: string, + storage?: string, + host: string = "localhost", + port: number = 7777, + debug: boolean = false +): Promise { + const url = `http://${host}:${port}/vms/${encodeURIComponent(vmName)}/update`; + + const body: LumeRunOptions = {}; + if (cpu !== undefined) { + body.cpu = cpu; + } + if (memory !== undefined) { + body.memory = memory; + } + if (storage) { + body.storage = storage; + } + + const command = `curl -s -X POST "${url}" -H "Content-Type: application/json" -d '${JSON.stringify( + body + )}'`; + + if (debug) { + console.log(`Executing: ${command}`); + } + + await executeCurl(command); +} + +/** + * Pull a VM image using Lume API. + */ +export async function lumeApiPull( + image: string, + host: string = "localhost", + port: number = 7777, + debug: boolean = false +): Promise { + const url = `http://${host}:${port}/images/pull`; + + const body: LumeRunOptions = { image }; + const command = `curl -s -X POST "${url}" -H "Content-Type: application/json" -d '${JSON.stringify( + body + )}'`; + + if (debug) { + console.log(`Executing: ${command}`); + } + + await executeCurl(command); +} diff --git a/libs/computer/typescript/src/providers/lumier/index.ts b/libs/computer/typescript/src/providers/lumier/index.ts new file mode 100644 index 00000000..33345619 --- /dev/null +++ b/libs/computer/typescript/src/providers/lumier/index.ts @@ -0,0 +1,16 @@ +/** + * Lumier VM provider implementation. + */ + +export let HAS_LUMIER = false; + +try { + // Check if Docker is available + const { execSync } = require("child_process"); + execSync("which docker", { stdio: "ignore" }); + HAS_LUMIER = true; +} catch { + HAS_LUMIER = false; +} + +export { LumierProvider } from "./provider"; diff --git a/libs/computer/typescript/src/providers/lumier/provider.ts b/libs/computer/typescript/src/providers/lumier/provider.ts new file mode 100644 index 00000000..3581b1fd --- /dev/null +++ b/libs/computer/typescript/src/providers/lumier/provider.ts @@ -0,0 +1,401 @@ +/** + * Lumier VM provider implementation. + * + * This provider uses Docker containers running the Lumier image to create + * macOS and Linux VMs. It handles VM lifecycle operations through Docker + * commands and container management. + */ + +import { exec } from "child_process"; +import { promisify } from "util"; +import { BaseVMProviderImpl, VMProviderType } from "../base"; +import { + lumeApiGet, + lumeApiRun, + lumeApiStop, + lumeApiUpdate, +} from "../lume_api"; + +const execAsync = promisify(exec); + +export interface LumierProviderOptions { + port?: number; + host?: string; + storage?: string; + sharedPath?: string; + image?: string; + verbose?: boolean; + ephemeral?: boolean; + noVNCPort?: number; +} + +export class LumierProvider extends BaseVMProviderImpl { + readonly providerType = VMProviderType.LUMIER; + + private host: string; + private apiPort: number; + private vncPort?: number; + private ephemeral: boolean; + private storage?: string; + private sharedPath?: string; + private image: string; + private verbose: boolean; + private containerName?: string; + private containerId?: string; + + constructor(options: LumierProviderOptions = {}) { + super(); + + this.host = options.host || "localhost"; + this.apiPort = options.port || 7777; + this.vncPort = options.noVNCPort; + this.ephemeral = options.ephemeral || false; + + // Handle ephemeral storage + if (this.ephemeral) { + this.storage = "ephemeral"; + } else { + this.storage = options.storage; + } + + this.sharedPath = options.sharedPath; + this.image = options.image || "macos-sequoia-cua:latest"; + this.verbose = options.verbose || false; + } + + /** + * Parse memory string to MB integer. + */ + private parseMemory(memoryStr: string | number): number { + if (typeof memoryStr === "number") { + return memoryStr; + } + + const match = memoryStr.match(/^(\d+)([A-Za-z]*)$/); + if (match) { + const value = parseInt(match[1] || "0"); + const unit = match[2]?.toUpperCase() || ""; + + if (unit === "GB" || unit === "G") { + return value * 1024; + } else if (unit === "MB" || unit === "M" || unit === "") { + return value; + } + } + + console.warn( + `Could not parse memory string '${memoryStr}', using 8GB default` + ); + return 8192; // Default to 8GB + } + + /** + * Check if a Docker container exists. + */ + private async containerExists(name: string): Promise { + try { + await execAsync(`docker inspect ${name}`); + return true; + } catch { + return false; + } + } + + /** + * Get container status. + */ + private async getContainerStatus(name: string): Promise { + try { + const { stdout } = await execAsync( + `docker inspect -f '{{.State.Status}}' ${name}` + ); + return stdout.trim(); + } catch { + return "not_found"; + } + } + + /** + * Start the Lumier container. + */ + private async startContainer( + name: string, + cpu: number = 4, + memory: string = "8GB", + runOpts: Record = {} + ): Promise { + const memoryMB = this.parseMemory(memory); + + // Build Docker run command + let dockerCmd = `docker run -d --name ${name}`; + + // Add resource limits + dockerCmd += ` --cpus=${cpu}`; + dockerCmd += ` --memory=${memoryMB}m`; + + // Add port mappings + dockerCmd += ` -p ${this.apiPort}:7777`; + if (this.vncPort) { + dockerCmd += ` -p ${this.vncPort}:8006`; + } + + // Add storage volume if not ephemeral + if (this.storage && this.storage !== "ephemeral") { + dockerCmd += ` -v ${this.storage}:/storage`; + } + + // Add shared path if specified + if (this.sharedPath) { + dockerCmd += ` -v ${this.sharedPath}:/shared`; + } + + // Add environment variables + if (runOpts.env) { + for (const [key, value] of Object.entries(runOpts.env)) { + dockerCmd += ` -e ${key}=${value}`; + } + } + + // Add the image + dockerCmd += ` ${this.image}`; + + if (this.verbose) { + console.log(`Starting container with command: ${dockerCmd}`); + } + + try { + const { stdout } = await execAsync(dockerCmd); + this.containerId = stdout.trim(); + this.containerName = name; + } catch (error: any) { + throw new Error(`Failed to start container: ${error.message}`); + } + } + + /** + * Wait for the API to be ready. + */ + private async waitForAPI(maxRetries: number = 30): Promise { + for (let i = 0; i < maxRetries; i++) { + try { + await lumeApiGet("", undefined, this.host, this.apiPort, false); + return; + } catch { + if (i === maxRetries - 1) { + throw new Error("API failed to become ready"); + } + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + } + } + + async getVM(name: string, storage?: string): Promise> { + // First check if container exists + const containerStatus = await this.getContainerStatus( + this.containerName || name + ); + + if (containerStatus === "not_found") { + throw new Error(`Container ${name} not found`); + } + + // If container is not running, return basic status + if (containerStatus !== "running") { + return { + name, + status: containerStatus, + ip: "", + cpu: 0, + memory: "0MB", + }; + } + + // Get VM info from API + return lumeApiGet( + name, + storage || this.storage, + this.host, + this.apiPort, + this.verbose + ); + } + + async listVMs(): Promise>> { + // Check if our container is running + if (!this.containerName) { + return []; + } + + const containerStatus = await this.getContainerStatus(this.containerName); + if (containerStatus !== "running") { + return []; + } + + // Get VMs from API + const response = await lumeApiGet( + "", + this.storage, + this.host, + this.apiPort, + this.verbose + ); + + if (Array.isArray(response)) { + return response; + } + + if (response.vms && Array.isArray(response.vms)) { + return response.vms; + } + + return []; + } + + async runVM( + image: string, + name: string, + runOpts: Record, + storage?: string + ): Promise> { + // Check if container already exists + const exists = await this.containerExists(name); + + if (exists) { + const status = await this.getContainerStatus(name); + if (status === "running") { + // Container already running, just run VM through API + return lumeApiRun( + image, + name, + runOpts, + storage || this.storage, + this.host, + this.apiPort, + this.verbose + ); + } else { + // Start existing container + await execAsync(`docker start ${name}`); + } + } else { + // Create and start new container + await this.startContainer( + name, + runOpts.cpu || 4, + runOpts.memory || "8GB", + runOpts + ); + } + + // Wait for API to be ready + await this.waitForAPI(); + + // Run VM through API + return lumeApiRun( + image, + name, + runOpts, + storage || this.storage, + this.host, + this.apiPort, + this.verbose + ); + } + + async stopVM(name: string, storage?: string): Promise { + // First stop VM through API + try { + await lumeApiStop( + name, + storage || this.storage, + this.host, + this.apiPort, + this.verbose + ); + } catch (error) { + if (this.verbose) { + console.log(`Failed to stop VM through API: ${error}`); + } + } + + // Stop the container + if (this.containerName) { + try { + await execAsync(`docker stop ${this.containerName}`); + + // Remove container if ephemeral + if (this.ephemeral) { + await execAsync(`docker rm ${this.containerName}`); + if (this.verbose) { + console.log( + `Container ${this.containerName} stopped and removed (ephemeral mode)` + ); + } + } + } catch (error: any) { + throw new Error(`Failed to stop container: ${error.message}`); + } + } + } + + async getIP( + name: string, + storage?: string, + retryDelay: number = 1 + ): Promise { + const maxRetries = 30; + + for (let i = 0; i < maxRetries; i++) { + try { + const vmInfo = await this.getVM(name, storage); + + if (vmInfo.ip && vmInfo.ip !== "") { + return vmInfo.ip; + } + + if (vmInfo.status === "stopped" || vmInfo.status === "error") { + throw new Error(`VM ${name} is in ${vmInfo.status} state`); + } + } catch (error) { + if (i === maxRetries - 1) { + throw error; + } + } + + await new Promise((resolve) => setTimeout(resolve, retryDelay * 1000)); + } + + throw new Error( + `Failed to get IP for VM ${name} after ${maxRetries} retries` + ); + } + + async updateVM( + name: string, + cpu?: number, + memory?: string, + storage?: string + ): Promise { + await lumeApiUpdate( + name, + cpu, + memory, + storage || this.storage, + this.host, + this.apiPort, + this.verbose + ); + } + + async __aexit__(excType: any, excVal: any, excTb: any): Promise { + // Clean up container if ephemeral + if (this.ephemeral && this.containerName) { + try { + await execAsync(`docker stop ${this.containerName}`); + await execAsync(`docker rm ${this.containerName}`); + } catch { + // Ignore errors during cleanup + } + } + } +} diff --git a/libs/computer/typescript/src/telemetry.ts b/libs/computer/typescript/src/telemetry.ts new file mode 100644 index 00000000..7f598721 --- /dev/null +++ b/libs/computer/typescript/src/telemetry.ts @@ -0,0 +1,115 @@ +/** + * Telemetry tracking for Computer usage. + */ + +interface TelemetryEvent { + event: string; + timestamp: Date; + properties?: Record; +} + +export class TelemetryManager { + private enabled: boolean = true; + private events: TelemetryEvent[] = []; + + setEnabled(enabled: boolean): void { + this.enabled = enabled; + } + + isEnabled(): boolean { + return this.enabled; + } + + track(event: string, properties?: Record): void { + if (!this.enabled) { + return; + } + + const telemetryEvent: TelemetryEvent = { + event, + timestamp: new Date(), + properties, + }; + + this.events.push(telemetryEvent); + + // In a real implementation, this would send to a telemetry service + // For now, just log to debug + if (process.env.NODE_ENV === "development") { + console.debug("[Telemetry]", event, properties); + } + } + + getEvents(): TelemetryEvent[] { + return [...this.events]; + } + + clear(): void { + this.events = []; + } +} + +// Singleton instance +const telemetryManager = new TelemetryManager(); + +/** + * Record computer initialization event + */ +export function recordComputerInitialization(): void { + telemetryManager.track("computer_initialized", { + timestamp: new Date().toISOString(), + version: process.env.npm_package_version || "unknown", + }); +} + +/** + * Record VM start event + */ +export function recordVMStart(vmName: string, provider: string): void { + telemetryManager.track("vm_started", { + vm_name: vmName, + provider, + timestamp: new Date().toISOString(), + }); +} + +/** + * Record VM stop event + */ +export function recordVMStop(vmName: string, duration: number): void { + telemetryManager.track("vm_stopped", { + vm_name: vmName, + duration_ms: duration, + timestamp: new Date().toISOString(), + }); +} + +/** + * Record interface action + */ +export function recordInterfaceAction( + action: string, + details?: Record +): void { + telemetryManager.track("interface_action", { + action, + ...details, + timestamp: new Date().toISOString(), + }); +} + +/** + * Set telemetry enabled/disabled + */ +export function setTelemetryEnabled(enabled: boolean): void { + telemetryManager.setEnabled(enabled); +} + +/** + * Check if telemetry is enabled + */ +export function isTelemetryEnabled(): boolean { + return telemetryManager.isEnabled(); +} + +export { telemetryManager }; diff --git a/libs/computer/typescript/src/utils.ts b/libs/computer/typescript/src/utils.ts new file mode 100644 index 00000000..fe1d398f --- /dev/null +++ b/libs/computer/typescript/src/utils.ts @@ -0,0 +1,118 @@ +/** + * Utility functions for the Computer module. + */ + +/** + * Sleep for a specified number of milliseconds + * @param ms Number of milliseconds to sleep + */ +export function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +/** + * Parse a display string in format "WIDTHxHEIGHT" + * @param display Display string to parse + * @returns Object with width and height + */ +export function parseDisplayString(display: string): { + width: number; + height: number; +} { + const match = display.match(/^(\d+)x(\d+)$/); + if (!match || !match[1] || !match[2]) { + throw new Error( + "Display string must be in format 'WIDTHxHEIGHT' (e.g. '1024x768')" + ); + } + return { + width: parseInt(match[1], 10), + height: parseInt(match[2], 10), + }; +} + +/** + * Validate image format (should be in format "image:tag") + * @param image Image string to validate + * @returns Object with image name and tag + */ +export function parseImageString(image: string): { name: string; tag: string } { + const parts = image.split(":"); + if (parts.length !== 2 || !parts[0] || !parts[1]) { + throw new Error("Image must be in the format :"); + } + return { + name: parts[0], + tag: parts[1], + }; +} + +/** + * Convert bytes to human-readable format + * @param bytes Number of bytes + * @returns Human-readable string + */ +export function formatBytes(bytes: number): string { + const units = ["B", "KB", "MB", "GB", "TB"]; + let size = bytes; + let unitIndex = 0; + + while (size >= 1024 && unitIndex < units.length - 1) { + size /= 1024; + unitIndex++; + } + + return `${size.toFixed(2)} ${units[unitIndex]}`; +} + +/** + * Parse memory string (e.g., "8GB") to bytes + * @param memory Memory string + * @returns Number of bytes + */ +export function parseMemoryString(memory: string): number { + const match = memory.match(/^(\d+)(B|KB|MB|GB|TB)?$/i); + if (!match || !match[1] || !match[2]) { + throw new Error("Invalid memory format. Use format like '8GB' or '1024MB'"); + } + + const value = parseInt(match[1], 10); + const unit = match[2].toUpperCase(); + + const multipliers: Record = { + B: 1, + KB: 1024, + MB: 1024 * 1024, + GB: 1024 * 1024 * 1024, + TB: 1024 * 1024 * 1024 * 1024, + }; + + return value * (multipliers[unit] || 1); +} + +/** + * Create a timeout promise that rejects after specified milliseconds + * @param ms Timeout in milliseconds + * @param message Optional error message + */ +export function timeout(ms: number, message?: string): Promise { + return new Promise((_, reject) => { + setTimeout(() => { + reject(new Error(message || `Timeout after ${ms}ms`)); + }, ms); + }); +} + +/** + * Race a promise against a timeout + * @param promise The promise to race + * @param ms Timeout in milliseconds + * @param message Optional timeout error message + */ +export async function withTimeout( + promise: Promise, + ms: number, + message?: string +): Promise { + return Promise.race([promise, timeout(ms, message)]); +} diff --git a/libs/computer/typescript/tsconfig.json b/libs/computer/typescript/tsconfig.json new file mode 100644 index 00000000..cdcd74de --- /dev/null +++ b/libs/computer/typescript/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": ["es2023"], + "moduleDetection": "force", + "module": "preserve", + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "types": ["node"], + "allowSyntheticDefaultImports": true, + "strict": true, + "noUnusedLocals": true, + "declaration": true, + "emitDeclarationOnly": true, + "esModuleInterop": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src"] +} diff --git a/libs/computer/typescript/tsdown.config.ts b/libs/computer/typescript/tsdown.config.ts new file mode 100644 index 00000000..19e6e5b9 --- /dev/null +++ b/libs/computer/typescript/tsdown.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "tsdown"; + +export default defineConfig([ + { + entry: ["./src/index.ts"], + platform: "node", + dts: true, + external: ["child_process", "util"], + }, +]); diff --git a/libs/computer/typescript/vitest.config.ts b/libs/computer/typescript/vitest.config.ts new file mode 100644 index 00000000..abed6b21 --- /dev/null +++ b/libs/computer/typescript/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({}) From 75c7a8bbc1d5aad120bbceddd188bbb2b0e6dc2b Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Thu, 12 Jun 2025 20:27:28 -0400 Subject: [PATCH 029/141] Use an actual logger --- libs/computer/typescript/package.json | 1 + libs/computer/typescript/pnpm-lock.yaml | 93 ++++++ .../typescript/src/computer/computer.ts | 313 +++++++++++------- libs/computer/typescript/src/logger.ts | 50 --- .../src/providers/lumier/provider.ts | 4 +- 5 files changed, 283 insertions(+), 178 deletions(-) diff --git a/libs/computer/typescript/package.json b/libs/computer/typescript/package.json index 3727210b..98ea7cf9 100644 --- a/libs/computer/typescript/package.json +++ b/libs/computer/typescript/package.json @@ -39,6 +39,7 @@ "prepublishOnly": "pnpm run build" }, "dependencies": { + "pino": "^9.7.0", "sharp": "^0.33.0" }, "devDependencies": { diff --git a/libs/computer/typescript/pnpm-lock.yaml b/libs/computer/typescript/pnpm-lock.yaml index 12d841f9..a0b97258 100644 --- a/libs/computer/typescript/pnpm-lock.yaml +++ b/libs/computer/typescript/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + pino: + specifier: ^9.7.0 + version: 9.7.0 sharp: specifier: ^0.33.0 version: 0.33.5 @@ -636,6 +639,10 @@ packages: resolution: {integrity: sha512-ROM2LlXbZBZVk97crfw8PGDOBzzsJvN2uJCmwswvPUNyfH14eg90mSN3xNqsri1JS1G9cz0VzeDUhxJkTrr4Ew==} engines: {node: '>=20.18.0'} + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + birpc@2.3.0: resolution: {integrity: sha512-ijbtkn/F3Pvzb6jHypHRyve2QApOCZDR25D/VnkY2G/lBNcXCTsnsCxgY4k4PkVB7zfwzYbY3O9Lcqe3xufS5g==} @@ -758,6 +765,10 @@ packages: exsolve@1.0.5: resolution: {integrity: sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==} + fast-redact@3.5.0: + resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} + engines: {node: '>=6'} + fdir@6.4.6: resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} peerDependencies: @@ -828,6 +839,10 @@ packages: ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + package-manager-detector@1.3.0: resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} @@ -848,6 +863,16 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} + pino-abstract-transport@2.0.0: + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} + + pino-std-serializers@7.0.0: + resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} + + pino@9.7.0: + resolution: {integrity: sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==} + hasBin: true + pkg-types@2.1.0: resolution: {integrity: sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==} @@ -855,9 +880,15 @@ packages: resolution: {integrity: sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==} engines: {node: ^10 || ^12 || >=14} + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + quansync@0.2.10: resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + rc9@2.1.2: resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} @@ -865,6 +896,10 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -898,6 +933,10 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + semver@7.7.2: resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} engines: {node: '>=10'} @@ -913,10 +952,17 @@ packages: simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + sonic-boom@4.2.0: + resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -926,6 +972,9 @@ packages: strip-literal@3.0.0: resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} + thread-stream@3.1.0: + resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -1514,6 +1563,8 @@ snapshots: '@babel/parser': 7.27.5 pathe: 2.0.3 + atomic-sleep@1.0.0: {} + birpc@2.3.0: {} bumpp@10.1.1: @@ -1647,6 +1698,8 @@ snapshots: exsolve@1.0.5: {} + fast-redact@3.5.0: {} + fdir@6.4.6(picomatch@4.0.2): optionalDependencies: picomatch: 4.0.2 @@ -1706,6 +1759,8 @@ snapshots: ohash@2.0.11: {} + on-exit-leak-free@2.1.2: {} + package-manager-detector@1.3.0: {} pathe@2.0.3: {} @@ -1718,6 +1773,26 @@ snapshots: picomatch@4.0.2: {} + pino-abstract-transport@2.0.0: + dependencies: + split2: 4.2.0 + + pino-std-serializers@7.0.0: {} + + pino@9.7.0: + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.5.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pino-std-serializers: 7.0.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.0 + thread-stream: 3.1.0 + pkg-types@2.1.0: dependencies: confbox: 0.2.2 @@ -1730,8 +1805,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + process-warning@5.0.0: {} + quansync@0.2.10: {} + quick-format-unescaped@4.0.4: {} + rc9@2.1.2: dependencies: defu: 6.1.4 @@ -1739,6 +1818,8 @@ snapshots: readdirp@4.1.2: {} + real-require@0.2.0: {} + resolve-pkg-maps@1.0.0: {} rolldown-plugin-dts@0.13.11(rolldown@1.0.0-beta.9)(typescript@5.8.3): @@ -1803,6 +1884,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.43.0 fsevents: 2.3.3 + safe-stable-stringify@2.5.0: {} + semver@7.7.2: {} sharp@0.33.5: @@ -1837,8 +1920,14 @@ snapshots: dependencies: is-arrayish: 0.3.2 + sonic-boom@4.2.0: + dependencies: + atomic-sleep: 1.0.0 + source-map-js@1.2.1: {} + split2@4.2.0: {} + stackback@0.0.2: {} std-env@3.9.0: {} @@ -1847,6 +1936,10 @@ snapshots: dependencies: js-tokens: 9.0.1 + thread-stream@3.1.0: + dependencies: + real-require: 0.2.0 + tinybench@2.9.0: {} tinyexec@0.3.2: {} diff --git a/libs/computer/typescript/src/computer/computer.ts b/libs/computer/typescript/src/computer/computer.ts index f4c9cffd..fea5cbb3 100644 --- a/libs/computer/typescript/src/computer/computer.ts +++ b/libs/computer/typescript/src/computer/computer.ts @@ -1,16 +1,25 @@ -import type { Display, ComputerConfig } from '../models'; -import type { BaseComputerInterface } from '../interface/base'; -import { InterfaceFactory } from '../interface/factory'; -import type { BaseVMProvider } from '../providers/base'; -import { VMProviderType } from '../providers/base'; -import { VMProviderFactory } from '../providers/factory'; -import { Logger, LogLevel } from '../logger'; -import { recordComputerInitialization, recordVMStart, recordVMStop } from '../telemetry'; -import { setDefaultComputer } from '../helpers'; -import { parseDisplayString, parseImageString, sleep, withTimeout } from '../utils'; -import sharp from 'sharp'; +import type { Display, ComputerConfig } from "../models"; +import type { BaseComputerInterface } from "../interface/base"; +import { InterfaceFactory } from "../interface/factory"; +import type { BaseVMProvider } from "../providers/base"; +import { VMProviderType } from "../providers/base"; +import { VMProviderFactory } from "../providers/factory"; +import pino from "pino"; +import { + recordComputerInitialization, + recordVMStart, + recordVMStop, +} from "../telemetry"; +import { setDefaultComputer } from "../helpers"; +import { + parseDisplayString, + parseImageString, + sleep, + withTimeout, +} from "../utils"; +import sharp from "sharp"; -export type OSType = 'macos' | 'linux' | 'windows'; +export type OSType = "macos" | "linux" | "windows"; export interface ComputerOptions { display?: Display | { width: number; height: number } | string; @@ -21,7 +30,7 @@ export interface ComputerOptions { image?: string; sharedDirectories?: string[]; useHostComputerServer?: boolean; - verbosity?: number | LogLevel; + verbosity?: pino.Level; telemetryEnabled?: boolean; providerType?: VMProviderType | string; port?: number; @@ -37,10 +46,8 @@ export interface ComputerOptions { * Computer is the main class for interacting with the computer. */ export class Computer { - private logger: Logger; - private vmLogger: Logger; - private interfaceLogger: Logger; - + private logger: pino.Logger; + private image: string; private port?: number; private noVNCPort?: number; @@ -56,7 +63,7 @@ export class Computer { private _telemetryEnabled: boolean; private _initialized: boolean = false; private _running: boolean = false; - private verbosity: number | LogLevel; + private useHostComputerServer: boolean; private config?: ComputerConfig; private _providerContext?: BaseVMProvider; @@ -71,29 +78,28 @@ export class Computer { */ constructor(options: ComputerOptions = {}) { const { - display = '1024x768', - memory = '8GB', - cpu = '4', - osType = 'macos', - name = '', - image = 'macos-sequoia-cua:latest', + display = "1024x768", + memory = "8GB", + cpu = "4", + osType = "macos", + name = "", + image = "macos-sequoia-cua:latest", sharedDirectories = [], useHostComputerServer = false, - verbosity = LogLevel.NORMAL, + verbosity = "info", telemetryEnabled = true, providerType = VMProviderType.LUME, port = 7777, noVNCPort = 8006, - host = process.env.PYLUME_HOST || 'localhost', + host = process.env.PYLUME_HOST || "localhost", storage, ephemeral = false, apiKey, - experiments = [] + experiments = [], } = options; - this.verbosity = verbosity; - this.logger = new Logger('cua.computer', verbosity); - this.logger.info('Initializing Computer...'); + this.logger = pino({ name: "cua.computer", level: verbosity }); + this.logger.info("Initializing Computer..."); // Store original parameters this.image = image; @@ -106,46 +112,46 @@ export class Computer { this.apiKey = apiKey; this.experiments = experiments; - if (this.experiments.includes('app-use')) { - if (this.osType !== 'macos') { - throw new Error('App use experiment is only supported on macOS'); + if (this.experiments.includes("app-use")) { + if (this.osType !== "macos") { + throw new Error("App use experiment is only supported on macOS"); } } // The default is currently to use non-ephemeral storage - if (storage && ephemeral && storage !== 'ephemeral') { - throw new Error('Storage path and ephemeral flag cannot be used together'); + if (storage && ephemeral && storage !== "ephemeral") { + throw new Error( + "Storage path and ephemeral flag cannot be used together" + ); } - this.storage = ephemeral ? 'ephemeral' : storage; + this.storage = ephemeral ? "ephemeral" : storage; // For Lumier provider, store the first shared directory path to use // for VM file sharing this.sharedPath = undefined; if (sharedDirectories && sharedDirectories.length > 0) { this.sharedPath = sharedDirectories[0]; - this.logger.info(`Using first shared directory for VM file sharing: ${this.sharedPath}`); + this.logger.info( + `Using first shared directory for VM file sharing: ${this.sharedPath}` + ); } // Store telemetry preference this._telemetryEnabled = telemetryEnabled; - // Configure component loggers with proper hierarchy - this.vmLogger = new Logger('cua.vm', verbosity); - this.interfaceLogger = new Logger('cua.interface', verbosity); - this.useHostComputerServer = useHostComputerServer; if (!useHostComputerServer) { const imageInfo = parseImageString(image); - - const vmName = name || image.replace(':', '_'); + + const vmName = name || image.replace(":", "_"); // Convert display parameter to Display object let displayConfig: Display; - if (typeof display === 'string') { + if (typeof display === "string") { const { width, height } = parseDisplayString(display); displayConfig = { width, height }; - } else if ('width' in display && 'height' in display) { + } else if ("width" in display && "height" in display) { displayConfig = display as Display; } else { displayConfig = display as Display; @@ -168,7 +174,9 @@ export class Computer { if (telemetryEnabled) { recordComputerInitialization(); } else { - this.logger.debug('Telemetry disabled - skipping initialization tracking'); + this.logger.debug( + "Telemetry disabled - skipping initialization tracking" + ); } } @@ -180,11 +188,13 @@ export class Computer { * @returns A proxy object with the Diorama interface, but using diorama_cmds. */ createDesktopFromApps(apps: string[]): any { - if (!this.experiments.includes('app-use')) { - throw new Error("App Usage is an experimental feature. Enable it by passing experiments=['app-use'] to Computer()"); + if (!this.experiments.includes("app-use")) { + throw new Error( + "App Usage is an experimental feature. Enable it by passing experiments=['app-use'] to Computer()" + ); } // DioramaComputer would be imported and used here - throw new Error('DioramaComputer not yet implemented'); + throw new Error("DioramaComputer not yet implemented"); } /** @@ -208,11 +218,11 @@ export class Computer { async run(): Promise { // If already initialized, just log and return if (this._initialized) { - this.logger.info('Computer already initialized, skipping initialization'); + this.logger.info("Computer already initialized, skipping initialization"); return; } - this.logger.info('Starting computer...'); + this.logger.info("Starting computer..."); const startTime = Date.now(); try { @@ -220,29 +230,32 @@ export class Computer { // If using host computer server if (this.useHostComputerServer) { - this.logger.info('Using host computer server'); - ipAddress = 'localhost'; - + this.logger.info("Using host computer server"); + ipAddress = "localhost"; + // Create the interface this._interface = InterfaceFactory.createInterfaceForOS( this.osType, ipAddress ); - this.logger.info('Waiting for host computer server to be ready...'); + this.logger.info("Waiting for host computer server to be ready..."); await this._interface.waitForReady(); - this.logger.info('Host computer server ready'); + this.logger.info("Host computer server ready"); } else { // Start or connect to VM this.logger.info(`Starting VM: ${this.image}`); - + if (!this._providerContext) { try { - const providerTypeName = typeof this.providerType === 'object' - ? this.providerType - : this.providerType; - - this.logger.verbose(`Initializing ${providerTypeName} provider context...`); + const providerTypeName = + typeof this.providerType === "object" + ? this.providerType + : this.providerType; + + this.logger.info( + `Initializing ${providerTypeName} provider context...` + ); // Create VM provider instance with explicit parameters const providerOptions = { @@ -251,14 +264,15 @@ export class Computer { storage: this.storage, sharedPath: this.sharedPath, image: this.image, - verbose: this.verbosity >= LogLevel.DEBUG, + verbose: + this.logger.level === "debug" || this.logger.level === "trace", ephemeral: this.ephemeral, noVNCPort: this.noVNCPort, - apiKey: this.apiKey + apiKey: this.apiKey, }; if (!this.config) { - throw new Error('Computer config not initialized'); + throw new Error("Computer config not initialized"); } this.config.vm_provider = await VMProviderFactory.createProvider( @@ -267,33 +281,38 @@ export class Computer { ); this._providerContext = await this.config.vm_provider.__aenter__(); - this.logger.verbose('VM provider context initialized successfully'); + this.logger.debug("VM provider context initialized successfully"); } catch (error) { - this.logger.error(`Failed to import provider dependencies: ${error}`); + this.logger.error( + `Failed to import provider dependencies: ${error}` + ); throw error; } } // Run the VM if (!this.config || !this.config.vm_provider) { - throw new Error('VM provider not initialized'); + throw new Error("VM provider not initialized"); } const runOpts = { display: this.config.display, memory: this.config.memory, cpu: this.config.cpu, - shared_directories: this.sharedDirectories + shared_directories: this.sharedDirectories, }; - this.logger.info(`Running VM ${this.config.name} with options:`, runOpts); - + this.logger.info( + `Running VM ${this.config.name} with options:`, + runOpts + ); + if (this._telemetryEnabled) { recordVMStart(this.config.name, String(this.providerType)); } - const storageParam = this.ephemeral ? 'ephemeral' : this.storage; - + const storageParam = this.ephemeral ? "ephemeral" : this.storage; + try { await this.config.vm_provider.runVM( this.image, @@ -302,7 +321,7 @@ export class Computer { storageParam ); } catch (error: any) { - if (error.message?.includes('already running')) { + if (error.message?.includes("already running")) { this.logger.info(`VM ${this.config.name} is already running`); } else { throw error; @@ -311,9 +330,9 @@ export class Computer { // Wait for VM to be ready try { - this.logger.info('Waiting for VM to be ready...'); + this.logger.info("Waiting for VM to be ready..."); await this.waitVMReady(); - + // Get IP address ipAddress = await this.getIP(); this.logger.info(`VM is ready with IP: ${ipAddress}`); @@ -326,14 +345,22 @@ export class Computer { // Initialize the interface try { // Verify we have a valid IP before initializing the interface - if (!ipAddress || ipAddress === 'unknown' || ipAddress === '0.0.0.0') { - throw new Error(`Cannot initialize interface - invalid IP address: ${ipAddress}`); + if (!ipAddress || ipAddress === "unknown" || ipAddress === "0.0.0.0") { + throw new Error( + `Cannot initialize interface - invalid IP address: ${ipAddress}` + ); } - this.logger.info(`Initializing interface for ${this.osType} at ${ipAddress}`); + this.logger.info( + `Initializing interface for ${this.osType} at ${ipAddress}` + ); // Pass authentication credentials if using cloud provider - if (this.providerType === VMProviderType.CLOUD && this.apiKey && this.config?.name) { + if ( + this.providerType === VMProviderType.CLOUD && + this.apiKey && + this.config?.name + ) { this._interface = InterfaceFactory.createInterfaceForOS( this.osType, ipAddress, @@ -348,7 +375,7 @@ export class Computer { } // Wait for the WebSocket interface to be ready - this.logger.info('Connecting to WebSocket interface...'); + this.logger.info("Connecting to WebSocket interface..."); try { await withTimeout( @@ -356,9 +383,11 @@ export class Computer { 30000, `Could not connect to WebSocket interface at ${ipAddress}:8000/ws` ); - this.logger.info('WebSocket interface connected successfully'); + this.logger.info("WebSocket interface connected successfully"); } catch (error) { - this.logger.error(`Failed to connect to WebSocket interface at ${ipAddress}`); + this.logger.error( + `Failed to connect to WebSocket interface at ${ipAddress}` + ); throw error; } @@ -366,13 +395,13 @@ export class Computer { if (!this.useHostComputerServer) { // In TypeScript, we'll use a Promise instead of asyncio.Event let resolveStop: () => void; - this._stopEvent = new Promise(resolve => { + this._stopEvent = new Promise((resolve) => { resolveStop = resolve; }); this._keepAliveTask = this._stopEvent; } - this.logger.info('Computer is ready'); + this.logger.info("Computer is ready"); // Set the initialization flag this._initialized = true; @@ -380,13 +409,15 @@ export class Computer { // Set this instance as the default computer for remote decorators setDefaultComputer(this); - this.logger.info('Computer successfully initialized'); + this.logger.info("Computer successfully initialized"); } catch (error) { throw error; } finally { // Log initialization time for performance monitoring const durationMs = Date.now() - startTime; - this.logger.debug(`Computer initialization took ${durationMs.toFixed(2)}ms`); + this.logger.debug( + `Computer initialization took ${durationMs.toFixed(2)}ms` + ); } } catch (error) { this.logger.error(`Failed to initialize computer: ${error}`); @@ -413,34 +444,37 @@ export class Computer { const startTime = Date.now(); try { - this.logger.info('Stopping Computer...'); + this.logger.info("Stopping Computer..."); // In VM mode, first explicitly stop the VM, then exit the provider context - if (!this.useHostComputerServer && this._providerContext && this.config?.vm_provider) { + if ( + !this.useHostComputerServer && + this._providerContext && + this.config?.vm_provider + ) { try { this.logger.info(`Stopping VM ${this.config.name}...`); - await this.config.vm_provider.stopVM( - this.config.name, - this.storage - ); + await this.config.vm_provider.stopVM(this.config.name, this.storage); } catch (error) { this.logger.error(`Error stopping VM: ${error}`); } - this.logger.verbose('Closing VM provider context...'); + this.logger.info("Closing VM provider context..."); await this.config.vm_provider.__aexit__(null, null, null); this._providerContext = undefined; } await this.disconnect(); - this.logger.info('Computer stopped'); + this.logger.info("Computer stopped"); } catch (error) { this.logger.debug(`Error during cleanup: ${error}`); } finally { // Log stop time for performance monitoring const durationMs = Date.now() - startTime; - this.logger.debug(`Computer stop process took ${durationMs.toFixed(2)}ms`); - + this.logger.debug( + `Computer stop process took ${durationMs.toFixed(2)}ms` + ); + if (this._telemetryEnabled && this.config?.name) { recordVMStop(this.config.name, durationMs); } @@ -450,22 +484,27 @@ export class Computer { /** * Get the IP address of the VM or localhost if using host computer server. */ - async getIP(maxRetries: number = 15, retryDelay: number = 2): Promise { + async getIP( + maxRetries: number = 15, + retryDelay: number = 2 + ): Promise { // For host computer server, always return localhost immediately if (this.useHostComputerServer) { - return '127.0.0.1'; + return "127.0.0.1"; } // Get IP from the provider if (!this.config?.vm_provider) { - throw new Error('VM provider is not initialized'); + throw new Error("VM provider is not initialized"); } // Log that we're waiting for the IP - this.logger.info(`Waiting for VM ${this.config.name} to get an IP address...`); + this.logger.info( + `Waiting for VM ${this.config.name} to get an IP address...` + ); // Call the provider's get_ip method which will wait indefinitely - const storageParam = this.ephemeral ? 'ephemeral' : this.storage; + const storageParam = this.ephemeral ? "ephemeral" : this.storage; // Log the image being used this.logger.info(`Running VM using image: ${this.image}`); @@ -496,16 +535,18 @@ export class Computer { let lastStatus: string | undefined; let attempts = 0; - this.logger.info(`Waiting for VM ${this.config?.name} to be ready (timeout: ${timeout}s)...`); + this.logger.info( + `Waiting for VM ${this.config?.name} to be ready (timeout: ${timeout}s)...` + ); - while ((Date.now() / 1000) - startTime < timeout) { + while (Date.now() / 1000 - startTime < timeout) { attempts++; - const elapsed = (Date.now() / 1000) - startTime; + const elapsed = Date.now() / 1000 - startTime; try { // Keep polling for VM info if (!this.config?.vm_provider) { - this.logger.error('VM provider is not initialized'); + this.logger.error("VM provider is not initialized"); return undefined; } @@ -522,14 +563,23 @@ export class Computer { const currentStatus = vm?.status; if (currentStatus !== lastStatus) { this.logger.info( - `VM status changed to: ${currentStatus} (after ${elapsed.toFixed(1)}s)` + `VM status changed to: ${currentStatus} (after ${elapsed.toFixed( + 1 + )}s)` ); lastStatus = currentStatus; } // Check if VM is ready - if (vm && vm.status === 'running' && vm.ip_address && vm.ip_address !== '0.0.0.0') { - this.logger.info(`VM ${this.config.name} is ready with IP: ${vm.ip_address}`); + if ( + vm && + vm.status === "running" && + vm.ip_address && + vm.ip_address !== "0.0.0.0" + ) { + this.logger.info( + `VM ${this.config.name} is ready with IP: ${vm.ip_address}` + ); return vm; } @@ -541,7 +591,9 @@ export class Computer { } } - throw new Error(`VM ${this.config?.name} failed to become ready within ${timeout} seconds`); + throw new Error( + `VM ${this.config?.name} failed to become ready within ${timeout} seconds` + ); } /** @@ -549,12 +601,12 @@ export class Computer { */ async update(cpu?: number, memory?: string): Promise { if (this.useHostComputerServer) { - this.logger.warning('Cannot update settings for host computer server'); + this.logger.warn("Cannot update settings for host computer server"); return; } if (!this.config?.vm_provider) { - throw new Error('VM provider is not initialized'); + throw new Error("VM provider is not initialized"); } await this.config.vm_provider.updateVM( @@ -568,11 +620,13 @@ export class Computer { /** * Get the dimensions of a screenshot. */ - async getScreenshotSize(screenshot: Buffer): Promise<{ width: number; height: number }> { + async getScreenshotSize( + screenshot: Buffer + ): Promise<{ width: number; height: number }> { const metadata = await sharp(screenshot).metadata(); return { width: metadata.width || 0, - height: metadata.height || 0 + height: metadata.height || 0, }; } @@ -581,7 +635,7 @@ export class Computer { */ get interface(): BaseComputerInterface { if (!this._interface) { - throw new Error('Computer interface not initialized. Call run() first.'); + throw new Error("Computer interface not initialized. Call run() first."); } return this._interface; } @@ -598,18 +652,18 @@ export class Computer { */ toScreenCoordinates(x: number, y: number): [number, number] { if (!this.config?.display) { - throw new Error('Display configuration not available'); + throw new Error("Display configuration not available"); } - return [ - x * this.config.display.width, - y * this.config.display.height - ]; + return [x * this.config.display.width, y * this.config.display.height]; } /** * Convert screen coordinates to screenshot coordinates. */ - async toScreenshotCoordinates(x: number, y: number): Promise<[number, number]> { + async toScreenshotCoordinates( + x: number, + y: number + ): Promise<[number, number]> { // In the Python version, this uses the interface to get screenshot dimensions // For now, we'll assume 1:1 mapping return [x, y]; @@ -618,10 +672,13 @@ export class Computer { /** * Install packages in a virtual environment. */ - async venvInstall(venvName: string, requirements: string[]): Promise<[string, string]> { + async venvInstall( + venvName: string, + requirements: string[] + ): Promise<[string, string]> { // This would be implemented using the interface to run commands // TODO: Implement venvInstall - throw new Error('venvInstall not yet implemented'); + throw new Error("venvInstall not yet implemented"); } /** @@ -630,15 +687,19 @@ export class Computer { async venvCmd(venvName: string, command: string): Promise<[string, string]> { // This would be implemented using the interface to run commands // TODO: Implement venvCmd - throw new Error('venvCmd not yet implemented'); + throw new Error("venvCmd not yet implemented"); } /** * Execute function in a virtual environment using source code extraction. */ - async venvExec(venvName: string, pythonFunc: Function, ...args: any[]): Promise { + async venvExec( + venvName: string, + pythonFunc: Function, + ...args: any[] + ): Promise { // This would be implemented using the interface to run Python code // TODO: Implement venvExec - throw new Error('venvExec not yet implemented'); + throw new Error("venvExec not yet implemented"); } } diff --git a/libs/computer/typescript/src/logger.ts b/libs/computer/typescript/src/logger.ts index 83f74425..e69de29b 100644 --- a/libs/computer/typescript/src/logger.ts +++ b/libs/computer/typescript/src/logger.ts @@ -1,50 +0,0 @@ -/** - * Logger implementation for the Computer library. - */ - -export enum LogLevel { - DEBUG = 10, - VERBOSE = 15, - INFO = 20, - NORMAL = 20, - WARNING = 30, - ERROR = 40, -} - -export class Logger { - private name: string; - private verbosity: number; - - constructor(name: string, verbosity: number | LogLevel = LogLevel.NORMAL) { - this.name = name; - this.verbosity = typeof verbosity === 'number' ? verbosity : verbosity; - } - - private log(level: LogLevel, message: string, ...args: any[]): void { - if (level >= this.verbosity) { - const timestamp = new Date().toISOString(); - const levelName = LogLevel[level]; - console.log(`[${timestamp}] [${this.name}] [${levelName}] ${message}`, ...args); - } - } - - debug(message: string, ...args: any[]): void { - this.log(LogLevel.DEBUG, message, ...args); - } - - info(message: string, ...args: any[]): void { - this.log(LogLevel.INFO, message, ...args); - } - - verbose(message: string, ...args: any[]): void { - this.log(LogLevel.VERBOSE, message, ...args); - } - - warning(message: string, ...args: any[]): void { - this.log(LogLevel.WARNING, message, ...args); - } - - error(message: string, ...args: any[]): void { - this.log(LogLevel.ERROR, message, ...args); - } -} diff --git a/libs/computer/typescript/src/providers/lumier/provider.ts b/libs/computer/typescript/src/providers/lumier/provider.ts index 3581b1fd..b9a855a1 100644 --- a/libs/computer/typescript/src/providers/lumier/provider.ts +++ b/libs/computer/typescript/src/providers/lumier/provider.ts @@ -73,8 +73,8 @@ export class LumierProvider extends BaseVMProviderImpl { const match = memoryStr.match(/^(\d+)([A-Za-z]*)$/); if (match) { - const value = parseInt(match[1] || "0"); - const unit = match[2]?.toUpperCase() || ""; + const value = parseInt(match[1]); + const unit = match[2].toUpperCase(); if (unit === "GB" || unit === "G") { return value * 1024; From efb8850fb79b28da373b86ac371da8ed0d1d52a4 Mon Sep 17 00:00:00 2001 From: f-trycua Date: Thu, 12 Jun 2025 20:11:34 -0700 Subject: [PATCH 030/141] Fix devcontainer docs for windsurf --- .devcontainer/devcontainer.json | 2 -- .devcontainer/post-install.sh | 0 README.md | 24 ++++++------------------ 3 files changed, 6 insertions(+), 20 deletions(-) mode change 100644 => 100755 .devcontainer/post-install.sh diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b3e80233..b30a33af 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -28,7 +28,6 @@ "python.terminal.activateEnvironment": false, "[python]": { "editor.formatOnSave": true, - "editor.defaultFormatter": "ms-python.black-formatter", "editor.codeActionsOnSave": { "source.organizeImports": "explicit" } @@ -45,6 +44,5 @@ } } }, - // Automatically run post-install.sh after container is created "postCreateCommand": "/bin/bash .devcontainer/post-install.sh" } diff --git a/.devcontainer/post-install.sh b/.devcontainer/post-install.sh old mode 100644 new mode 100755 diff --git a/README.md b/README.md index d40b9aa1..63627063 100644 --- a/README.md +++ b/README.md @@ -70,15 +70,12 @@ This script will guide you through setup and launch the Computer-Use Agent UI. This repository includes a [Dev Container](./.devcontainer/README.md) configuration that simplifies setup to a few steps: -1. **Install Dev Containers extension** in VS Code -2. **Clone and open in container:** - - Press `Ctrl+Shift+P` (or `Cmd+Shift+P` on Mac) - - Type `Dev Containers: Clone Repository in Container Volume...` - - Paste the repository URL: `https://github.com/trycua/cua.git` - - Open the `.vscode/py.code-workspace` workspace -3. **Run the Agent UI example:** Click image to start the Gradio UI - -The Gradio UI will be available at `http://localhost:7860` and will automatically forward to your host machine. +1. **Install the Dev Containers extension ([VS Code](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) or [WindSurf](https://docs.windsurf.com/windsurf/advanced#dev-containers-beta))** +2. **Open the repository in the Dev Container:** + - Press `Ctrl+Shift+P` (or `Cmd+Shift+P` on macOS) + - Select `Dev Containers: Clone Repository in Container Volume...` and paste the repository URL: `https://github.com/trycua/cua.git` (if not cloned) or `Dev Containers: Open Folder in Container...` (if already cloned). **Note**: On WindSurf, the post install hook might not run automatically. If so, run `/bin/bash .devcontainer/post-install.sh` manually. +3. **Run the Agent UI example:** Click image to start the Gradio UI. If prompted to install **debugpy (Python Debugger)** to enable remote debugging, select 'Yes' to proceed. +4. **Access the Gradio UI:** The Gradio UI will be available at `http://localhost:7860` and will automatically forward to your host machine. --- @@ -221,7 +218,6 @@ docker run -it --rm \ | [**Agent**](./libs/agent/README.md) | AI agent framework for automating tasks | `pip install "cua-agent[all]"` | | [**MCP Server**](./libs/mcp-server/README.md) | MCP server for using CUA with Claude Desktop | `pip install cua-mcp-server` | | [**SOM**](./libs/som/README.md) | Self-of-Mark library for Agent | `pip install cua-som` | -| [**PyLume**](./libs/pylume/README.md) | Python bindings for Lume | `pip install pylume` | | [**Computer Server**](./libs/computer-server/README.md) | Server component for Computer | `pip install cua-computer-server` | | [**Core**](./libs/core/README.md) | Core utilities | `pip install cua-core` | @@ -378,14 +374,6 @@ Thank you to all our supporters! Ricter Zheng
Ricter Zheng

💻 Rahul Karajgikar
Rahul Karajgikar

💻 trospix
trospix

💻 - Ikko Eltociear Ashimine
Ikko Eltociear Ashimine

💻 - 한석호(MilKyo)
한석호(MilKyo)

💻 - - - Rahim Nathwani
Rahim Nathwani

💻 - Matt Speck
Matt Speck

💻 - FinnBorge
FinnBorge

💻 - Jakub Klapacz
Jakub Klapacz

💻 Evan smith
Evan smith

💻 From bdbca73e522aed1a62772f5bbfcecb4187d2498c Mon Sep 17 00:00:00 2001 From: f-trycua Date: Thu, 12 Jun 2025 20:12:24 -0700 Subject: [PATCH 031/141] Restore defaultFormatter --- .devcontainer/devcontainer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b30a33af..4f9ceccc 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -28,6 +28,7 @@ "python.terminal.activateEnvironment": false, "[python]": { "editor.formatOnSave": true, + "editor.defaultFormatter": "ms-python.black-formatter", "editor.codeActionsOnSave": { "source.organizeImports": "explicit" } From d21a4ca2946bf1cfcbcca5af912f31f49ea5650c Mon Sep 17 00:00:00 2001 From: ddupont <3820588+ddupont808@users.noreply.github.com> Date: Fri, 13 Jun 2025 10:14:49 -0400 Subject: [PATCH 032/141] Update README.md graphic --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 63627063..cf1e8004 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,8 @@ This repository includes a [Dev Container](./.devcontainer/README.md) configurat 2. **Open the repository in the Dev Container:** - Press `Ctrl+Shift+P` (or `Cmd+Shift+P` on macOS) - Select `Dev Containers: Clone Repository in Container Volume...` and paste the repository URL: `https://github.com/trycua/cua.git` (if not cloned) or `Dev Containers: Open Folder in Container...` (if already cloned). **Note**: On WindSurf, the post install hook might not run automatically. If so, run `/bin/bash .devcontainer/post-install.sh` manually. -3. **Run the Agent UI example:** Click image to start the Gradio UI. If prompted to install **debugpy (Python Debugger)** to enable remote debugging, select 'Yes' to proceed. +3. **Run the Agent UI example:** Click ![Run Agent UI](https://github.com/user-attachments/assets/7a61ef34-4b22-4dab-9864-f86bf83e290b) + to start the Gradio UI. If prompted to install **debugpy (Python Debugger)** to enable remote debugging, select 'Yes' to proceed. 4. **Access the Gradio UI:** The Gradio UI will be available at `http://localhost:7860` and will automatically forward to your host machine. --- From 84de38bac9224480fda6f2eaeb964a68dbeff0bb Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Fri, 13 Jun 2025 11:36:46 -0400 Subject: [PATCH 033/141] Add windows computer-server --- .../computer_server/handlers/factory.py | 22 +- .../computer_server/handlers/windows.py | 405 ++++++++++++++++++ libs/computer-server/pyproject.toml | 8 +- 3 files changed, 425 insertions(+), 10 deletions(-) create mode 100644 libs/computer-server/computer_server/handlers/windows.py diff --git a/libs/computer-server/computer_server/handlers/factory.py b/libs/computer-server/computer_server/handlers/factory.py index 5a9dc414..31d02fee 100644 --- a/libs/computer-server/computer_server/handlers/factory.py +++ b/libs/computer-server/computer_server/handlers/factory.py @@ -11,6 +11,8 @@ if system == 'darwin': from computer_server.diorama.macos import MacOSDioramaHandler elif system == 'linux': from .linux import LinuxAccessibilityHandler, LinuxAutomationHandler +elif system == 'windows': + from .windows import WindowsAccessibilityHandler, WindowsAutomationHandler from .generic import GenericFileHandler @@ -22,7 +24,7 @@ class HandlerFactory: """Determine the current OS. Returns: - str: The OS type ('darwin' for macOS or 'linux' for Linux) + str: The OS type ('darwin' for macOS, 'linux' for Linux, or 'windows' for Windows) Raises: RuntimeError: If unable to determine the current OS @@ -31,13 +33,15 @@ class HandlerFactory: # Use platform.system() as primary method system = platform.system().lower() if system in ['darwin', 'linux', 'windows']: - return 'darwin' if system == 'darwin' else 'linux' if system == 'linux' else 'windows' + return system - # Fallback to uname if platform.system() doesn't return expected values - result = subprocess.run(['uname', '-s'], capture_output=True, text=True) - if result.returncode != 0: - raise RuntimeError(f"uname command failed: {result.stderr}") - return result.stdout.strip().lower() + # Fallback to uname if platform.system() doesn't return expected values (Unix-like systems only) + if system != 'windows': + result = subprocess.run(['uname', '-s'], capture_output=True, text=True) + if result.returncode == 0: + return result.stdout.strip().lower() + + raise RuntimeError(f"Unsupported OS: {system}") except Exception as e: raise RuntimeError(f"Failed to determine current OS: {str(e)}") @@ -59,5 +63,7 @@ class HandlerFactory: return MacOSAccessibilityHandler(), MacOSAutomationHandler(), MacOSDioramaHandler(), GenericFileHandler() elif os_type == 'linux': return LinuxAccessibilityHandler(), LinuxAutomationHandler(), BaseDioramaHandler(), GenericFileHandler() + elif os_type == 'windows': + return WindowsAccessibilityHandler(), WindowsAutomationHandler(), BaseDioramaHandler(), GenericFileHandler() else: - raise NotImplementedError(f"OS '{os_type}' is not supported") \ No newline at end of file + raise NotImplementedError(f"OS '{os_type}' is not supported") diff --git a/libs/computer-server/computer_server/handlers/windows.py b/libs/computer-server/computer_server/handlers/windows.py new file mode 100644 index 00000000..269b97b6 --- /dev/null +++ b/libs/computer-server/computer_server/handlers/windows.py @@ -0,0 +1,405 @@ +""" +Windows implementation of automation and accessibility handlers. + +This implementation uses pyautogui for GUI automation and Windows-specific APIs +for accessibility and system operations. +""" +from typing import Dict, Any, List, Tuple, Optional +import logging +import subprocess +import base64 +import os +from io import BytesIO + +# Configure logger +logger = logging.getLogger(__name__) + +# Try to import pyautogui +try: + import pyautogui + logger.info("pyautogui successfully imported, GUI automation available") +except Exception as e: + logger.error(f"pyautogui import failed: {str(e)}. GUI operations will not work.") + pyautogui = None + +# Try to import Windows-specific modules +try: + import win32gui + import win32con + import win32api + logger.info("Windows API modules successfully imported") + WINDOWS_API_AVAILABLE = True +except Exception as e: + logger.error(f"Windows API modules import failed: {str(e)}. Some Windows-specific features will be unavailable.") + WINDOWS_API_AVAILABLE = False + +from .base import BaseAccessibilityHandler, BaseAutomationHandler + +class WindowsAccessibilityHandler(BaseAccessibilityHandler): + """Windows implementation of accessibility handler.""" + + async def get_accessibility_tree(self) -> Dict[str, Any]: + """Get the accessibility tree of the current window.""" + if not WINDOWS_API_AVAILABLE: + return {"success": False, "error": "Windows API not available"} + + try: + # Get the foreground window + hwnd = win32gui.GetForegroundWindow() + if not hwnd: + return {"success": False, "error": "No foreground window found"} + + # Get window information + window_text = win32gui.GetWindowText(hwnd) + rect = win32gui.GetWindowRect(hwnd) + + tree = { + "role": "Window", + "title": window_text, + "position": {"x": rect[0], "y": rect[1]}, + "size": {"width": rect[2] - rect[0], "height": rect[3] - rect[1]}, + "children": [] + } + + # Enumerate child windows + def enum_child_proc(hwnd_child, children_list): + try: + child_text = win32gui.GetWindowText(hwnd_child) + child_rect = win32gui.GetWindowRect(hwnd_child) + child_class = win32gui.GetClassName(hwnd_child) + + child_info = { + "role": child_class, + "title": child_text, + "position": {"x": child_rect[0], "y": child_rect[1]}, + "size": {"width": child_rect[2] - child_rect[0], "height": child_rect[3] - child_rect[1]}, + "children": [] + } + children_list.append(child_info) + except Exception as e: + logger.debug(f"Error getting child window info: {e}") + return True + + win32gui.EnumChildWindows(hwnd, enum_child_proc, tree["children"]) + + return {"success": True, "tree": tree} + + except Exception as e: + logger.error(f"Error getting accessibility tree: {e}") + return {"success": False, "error": str(e)} + + async def find_element(self, role: Optional[str] = None, + title: Optional[str] = None, + value: Optional[str] = None) -> Dict[str, Any]: + """Find an element in the accessibility tree by criteria.""" + if not WINDOWS_API_AVAILABLE: + return {"success": False, "error": "Windows API not available"} + + try: + # Find window by title if specified + if title: + hwnd = win32gui.FindWindow(None, title) + if hwnd: + rect = win32gui.GetWindowRect(hwnd) + return { + "success": True, + "element": { + "role": "Window", + "title": title, + "position": {"x": rect[0], "y": rect[1]}, + "size": {"width": rect[2] - rect[0], "height": rect[3] - rect[1]} + } + } + + # Find window by class name if role is specified + if role: + hwnd = win32gui.FindWindow(role, None) + if hwnd: + window_text = win32gui.GetWindowText(hwnd) + rect = win32gui.GetWindowRect(hwnd) + return { + "success": True, + "element": { + "role": role, + "title": window_text, + "position": {"x": rect[0], "y": rect[1]}, + "size": {"width": rect[2] - rect[0], "height": rect[3] - rect[1]} + } + } + + return {"success": False, "error": "Element not found"} + + except Exception as e: + logger.error(f"Error finding element: {e}") + return {"success": False, "error": str(e)} + +class WindowsAutomationHandler(BaseAutomationHandler): + """Windows implementation of automation handler using pyautogui and Windows APIs.""" + + # Mouse Actions + async def mouse_down(self, x: Optional[int] = None, y: Optional[int] = None, button: str = "left") -> Dict[str, Any]: + if not pyautogui: + return {"success": False, "error": "pyautogui not available"} + + try: + if x is not None and y is not None: + pyautogui.moveTo(x, y) + pyautogui.mouseDown(button=button) + return {"success": True} + except Exception as e: + return {"success": False, "error": str(e)} + + async def mouse_up(self, x: Optional[int] = None, y: Optional[int] = None, button: str = "left") -> Dict[str, Any]: + if not pyautogui: + return {"success": False, "error": "pyautogui not available"} + + try: + if x is not None and y is not None: + pyautogui.moveTo(x, y) + pyautogui.mouseUp(button=button) + return {"success": True} + except Exception as e: + return {"success": False, "error": str(e)} + + async def move_cursor(self, x: int, y: int) -> Dict[str, Any]: + if not pyautogui: + return {"success": False, "error": "pyautogui not available"} + + try: + pyautogui.moveTo(x, y) + return {"success": True} + except Exception as e: + return {"success": False, "error": str(e)} + + async def left_click(self, x: Optional[int] = None, y: Optional[int] = None) -> Dict[str, Any]: + if not pyautogui: + return {"success": False, "error": "pyautogui not available"} + + try: + if x is not None and y is not None: + pyautogui.moveTo(x, y) + pyautogui.click() + return {"success": True} + except Exception as e: + return {"success": False, "error": str(e)} + + async def right_click(self, x: Optional[int] = None, y: Optional[int] = None) -> Dict[str, Any]: + if not pyautogui: + return {"success": False, "error": "pyautogui not available"} + + try: + if x is not None and y is not None: + pyautogui.moveTo(x, y) + pyautogui.rightClick() + return {"success": True} + except Exception as e: + return {"success": False, "error": str(e)} + + async def double_click(self, x: Optional[int] = None, y: Optional[int] = None) -> Dict[str, Any]: + if not pyautogui: + return {"success": False, "error": "pyautogui not available"} + + try: + if x is not None and y is not None: + pyautogui.moveTo(x, y) + pyautogui.doubleClick(interval=0.1) + return {"success": True} + except Exception as e: + return {"success": False, "error": str(e)} + + async def drag_to(self, x: int, y: int, button: str = "left", duration: float = 0.5) -> Dict[str, Any]: + if not pyautogui: + return {"success": False, "error": "pyautogui not available"} + + try: + pyautogui.dragTo(x, y, duration=duration, button=button) + return {"success": True} + except Exception as e: + return {"success": False, "error": str(e)} + + async def drag(self, path: List[Tuple[int, int]], button: str = "left", duration: float = 0.5) -> Dict[str, Any]: + if not pyautogui: + return {"success": False, "error": "pyautogui not available"} + + try: + if not path: + return {"success": False, "error": "Path is empty"} + + # Move to first position + pyautogui.moveTo(*path[0]) + + # Drag through all positions + for x, y in path[1:]: + pyautogui.dragTo(x, y, duration=duration/len(path), button=button) + + return {"success": True} + except Exception as e: + return {"success": False, "error": str(e)} + + # Keyboard Actions + async def key_down(self, key: str) -> Dict[str, Any]: + if not pyautogui: + return {"success": False, "error": "pyautogui not available"} + + try: + pyautogui.keyDown(key) + return {"success": True} + except Exception as e: + return {"success": False, "error": str(e)} + + async def key_up(self, key: str) -> Dict[str, Any]: + if not pyautogui: + return {"success": False, "error": "pyautogui not available"} + + try: + pyautogui.keyUp(key) + return {"success": True} + except Exception as e: + return {"success": False, "error": str(e)} + + async def type_text(self, text: str) -> Dict[str, Any]: + if not pyautogui: + return {"success": False, "error": "pyautogui not available"} + + try: + pyautogui.write(text) + return {"success": True} + except Exception as e: + return {"success": False, "error": str(e)} + + async def press_key(self, key: str) -> Dict[str, Any]: + if not pyautogui: + return {"success": False, "error": "pyautogui not available"} + + try: + pyautogui.press(key) + return {"success": True} + except Exception as e: + return {"success": False, "error": str(e)} + + async def hotkey(self, *keys: str) -> Dict[str, Any]: + if not pyautogui: + return {"success": False, "error": "pyautogui not available"} + + try: + pyautogui.hotkey(*keys) + return {"success": True} + except Exception as e: + return {"success": False, "error": str(e)} + + # Scrolling Actions + async def scroll(self, x: int, y: int) -> Dict[str, Any]: + if not pyautogui: + return {"success": False, "error": "pyautogui not available"} + + try: + # pyautogui.scroll() only takes one parameter (vertical scroll) + pyautogui.scroll(y) + return {"success": True} + except Exception as e: + return {"success": False, "error": str(e)} + + async def scroll_down(self, clicks: int = 1) -> Dict[str, Any]: + if not pyautogui: + return {"success": False, "error": "pyautogui not available"} + + try: + pyautogui.scroll(-clicks) + return {"success": True} + except Exception as e: + return {"success": False, "error": str(e)} + + async def scroll_up(self, clicks: int = 1) -> Dict[str, Any]: + if not pyautogui: + return {"success": False, "error": "pyautogui not available"} + + try: + pyautogui.scroll(clicks) + return {"success": True} + except Exception as e: + return {"success": False, "error": str(e)} + + # Screen Actions + async def screenshot(self) -> Dict[str, Any]: + if not pyautogui: + return {"success": False, "error": "pyautogui not available"} + + try: + from PIL import Image + screenshot = pyautogui.screenshot() + if not isinstance(screenshot, Image.Image): + return {"success": False, "error": "Failed to capture screenshot"} + + buffered = BytesIO() + screenshot.save(buffered, format="PNG", optimize=True) + buffered.seek(0) + image_data = base64.b64encode(buffered.getvalue()).decode() + return {"success": True, "image_data": image_data} + except Exception as e: + return {"success": False, "error": f"Screenshot error: {str(e)}"} + + async def get_screen_size(self) -> Dict[str, Any]: + try: + if pyautogui: + size = pyautogui.size() + return {"success": True, "size": {"width": size.width, "height": size.height}} + elif WINDOWS_API_AVAILABLE: + # Fallback to Windows API + width = win32api.GetSystemMetrics(win32con.SM_CXSCREEN) + height = win32api.GetSystemMetrics(win32con.SM_CYSCREEN) + return {"success": True, "size": {"width": width, "height": height}} + else: + return {"success": False, "error": "No screen size detection method available"} + except Exception as e: + return {"success": False, "error": str(e)} + + async def get_cursor_position(self) -> Dict[str, Any]: + try: + if pyautogui: + pos = pyautogui.position() + return {"success": True, "position": {"x": pos.x, "y": pos.y}} + elif WINDOWS_API_AVAILABLE: + # Fallback to Windows API + pos = win32gui.GetCursorPos() + return {"success": True, "position": {"x": pos[0], "y": pos[1]}} + else: + return {"success": False, "error": "No cursor position detection method available"} + except Exception as e: + return {"success": False, "error": str(e)} + + # Clipboard Actions + async def copy_to_clipboard(self) -> Dict[str, Any]: + try: + import pyperclip + content = pyperclip.paste() + return {"success": True, "content": content} + except Exception as e: + return {"success": False, "error": str(e)} + + async def set_clipboard(self, text: str) -> Dict[str, Any]: + try: + import pyperclip + pyperclip.copy(text) + return {"success": True} + except Exception as e: + return {"success": False, "error": str(e)} + + # Command Execution + async def run_command(self, command: str) -> Dict[str, Any]: + try: + # Use cmd.exe for Windows commands + process = subprocess.run( + command, + shell=True, + capture_output=True, + text=True, + creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0 + ) + return { + "success": True, + "stdout": process.stdout, + "stderr": process.stderr, + "return_code": process.returncode + } + except Exception as e: + return {"success": False, "error": str(e)} diff --git a/libs/computer-server/pyproject.toml b/libs/computer-server/pyproject.toml index cbf9821a..090da016 100644 --- a/libs/computer-server/pyproject.toml +++ b/libs/computer-server/pyproject.toml @@ -19,7 +19,8 @@ dependencies = [ "pyautogui>=0.9.54", "pynput>=1.8.1", "pillow>=10.2.0", - "aiohttp>=3.9.1" + "aiohttp>=3.9.1", + "pyperclip>=1.9.0" ] [project.optional-dependencies] @@ -31,6 +32,9 @@ macos = [ linux = [ "python-xlib>=0.33" ] +windows = [ + "pywin32>=310" +] [project.urls] homepage = "https://github.com/trycua/cua" @@ -80,4 +84,4 @@ disallow_untyped_defs = true check_untyped_defs = true warn_return_any = true show_error_codes = true -warn_unused_ignores = false \ No newline at end of file +warn_unused_ignores = false From 6a5bac8b197d2eb22a08d9c8a78b3dc2d45bb9b8 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Fri, 13 Jun 2025 11:39:49 -0400 Subject: [PATCH 034/141] Add to computer UI --- libs/computer/computer/ui/gradio/app.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/libs/computer/computer/ui/gradio/app.py b/libs/computer/computer/ui/gradio/app.py index b1d131d9..7825de3f 100644 --- a/libs/computer/computer/ui/gradio/app.py +++ b/libs/computer/computer/ui/gradio/app.py @@ -529,10 +529,10 @@ async def execute(name, action, arguments): return results async def handle_init_computer(os_choice: str, app_list=None, provider="lume"): - """Initialize the computer instance and tools for macOS or Ubuntu + """Initialize the computer instance and tools for macOS or Ubuntu or Windows Args: - os_choice: The OS to use ("macOS" or "Ubuntu") + os_choice: The OS to use ("macOS" or "Ubuntu" or "Windows") app_list: Optional list of apps to focus on using the app-use experiment provider: The provider to use ("lume" or "self") """ @@ -548,6 +548,9 @@ async def handle_init_computer(os_choice: str, app_list=None, provider="lume"): if os_choice == "Ubuntu": os_type_str = "linux" image_str = "ubuntu-noble-vanilla:latest" + elif os_choice == "Windows": + os_type_str = "windows" + image_str = "windows-11-vanilla:latest" else: os_type_str = "macos" image_str = "macos-sequoia-cua:latest" @@ -1065,7 +1068,7 @@ def create_gradio_ui(): with gr.Row(): os_choice = gr.Radio( label="OS", - choices=["macOS", "Ubuntu"], + choices=["macOS", "Ubuntu", "Windows"], value="macOS", interactive=False # disable until the ubuntu image is ready ) From 137b336e317fb3e159206ffc6826bb409c7d4515 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Fri, 13 Jun 2025 11:43:59 -0400 Subject: [PATCH 035/141] Added computer to client --- libs/computer/computer/interface/factory.py | 9 +- libs/computer/computer/interface/windows.py | 687 ++++++++++++++++++++ 2 files changed, 693 insertions(+), 3 deletions(-) create mode 100644 libs/computer/computer/interface/windows.py diff --git a/libs/computer/computer/interface/factory.py b/libs/computer/computer/interface/factory.py index 949972c4..3647400e 100644 --- a/libs/computer/computer/interface/factory.py +++ b/libs/computer/computer/interface/factory.py @@ -8,7 +8,7 @@ class InterfaceFactory: @staticmethod def create_interface_for_os( - os: Literal['macos', 'linux'], + os: Literal['macos', 'linux', 'windows'], ip_address: str, api_key: Optional[str] = None, vm_name: Optional[str] = None @@ -16,7 +16,7 @@ class InterfaceFactory: """Create an interface for the specified OS. Args: - os: Operating system type ('macos' or 'linux') + os: Operating system type ('macos', 'linux', or 'windows') ip_address: IP address of the computer to control api_key: Optional API key for cloud authentication vm_name: Optional VM name for cloud authentication @@ -30,10 +30,13 @@ class InterfaceFactory: # Import implementations here to avoid circular imports from .macos import MacOSComputerInterface from .linux import LinuxComputerInterface + from .windows import WindowsComputerInterface if os == 'macos': return MacOSComputerInterface(ip_address, api_key=api_key, vm_name=vm_name) elif os == 'linux': return LinuxComputerInterface(ip_address, api_key=api_key, vm_name=vm_name) + elif os == 'windows': + return WindowsComputerInterface(ip_address, api_key=api_key, vm_name=vm_name) else: - raise ValueError(f"Unsupported OS type: {os}") \ No newline at end of file + raise ValueError(f"Unsupported OS type: {os}") diff --git a/libs/computer/computer/interface/windows.py b/libs/computer/computer/interface/windows.py new file mode 100644 index 00000000..6acce1c1 --- /dev/null +++ b/libs/computer/computer/interface/windows.py @@ -0,0 +1,687 @@ +import asyncio +import json +import time +from typing import Any, Dict, List, Optional, Tuple +from PIL import Image + +import websockets + +from ..logger import Logger, LogLevel +from .base import BaseComputerInterface +from ..utils import decode_base64_image, encode_base64_image, bytes_to_image, draw_box, resize_image +from .models import Key, KeyType, MouseButton + + +class WindowsComputerInterface(BaseComputerInterface): + """Interface for Windows.""" + + def __init__(self, ip_address: str, username: str = "lume", password: str = "lume", api_key: Optional[str] = None, vm_name: Optional[str] = None): + super().__init__(ip_address, username, password, api_key, vm_name) + self._ws = None + self._reconnect_task = None + self._closed = False + self._last_ping = 0 + self._ping_interval = 5 # Send ping every 5 seconds + self._ping_timeout = 120 # Wait 120 seconds for pong response + self._reconnect_delay = 1 # Start with 1 second delay + self._max_reconnect_delay = 30 # Maximum delay between reconnection attempts + self._log_connection_attempts = True # Flag to control connection attempt logging + self._authenticated = False # Track authentication status + self._command_lock = asyncio.Lock() # Lock to ensure only one command at a time + + # Set logger name for Windows interface + self.logger = Logger("cua.interface.windows", LogLevel.NORMAL) + + @property + def ws_uri(self) -> str: + """Get the WebSocket URI using the current IP address. + + Returns: + WebSocket URI for the Computer API Server + """ + protocol = "wss" if self.api_key else "ws" + port = "8443" if self.api_key else "8000" + return f"{protocol}://{self.ip_address}:{port}/ws" + + async def _keep_alive(self): + """Keep the WebSocket connection alive with automatic reconnection.""" + retry_count = 0 + max_log_attempts = 1 # Only log the first attempt at INFO level + log_interval = 500 # Then log every 500th attempt (significantly increased from 30) + last_warning_time = 0 + min_warning_interval = 30 # Minimum seconds between connection lost warnings + min_retry_delay = 0.5 # Minimum delay between connection attempts (500ms) + + while not self._closed: + try: + if self._ws is None or ( + self._ws and self._ws.state == websockets.protocol.State.CLOSED + ): + try: + retry_count += 1 + + # Add a minimum delay between connection attempts to avoid flooding + if retry_count > 1: + await asyncio.sleep(min_retry_delay) + + # Only log the first attempt at INFO level, then every Nth attempt + if retry_count == 1: + self.logger.info(f"Attempting WebSocket connection to {self.ws_uri}") + elif retry_count % log_interval == 0: + self.logger.info( + f"Still attempting WebSocket connection (attempt {retry_count})..." + ) + else: + # All other attempts are logged at DEBUG level + self.logger.debug( + f"Attempting WebSocket connection to {self.ws_uri} (attempt {retry_count})" + ) + + self._ws = await asyncio.wait_for( + websockets.connect( + self.ws_uri, + max_size=1024 * 1024 * 10, # 10MB limit + max_queue=32, + ping_interval=self._ping_interval, + ping_timeout=self._ping_timeout, + close_timeout=5, + compression=None, # Disable compression to reduce overhead + ), + timeout=120, + ) + self.logger.info("WebSocket connection established") + + # Authentication will be handled by the first command that needs it + # Don't do authentication here to avoid recv conflicts + + self._reconnect_delay = 1 # Reset reconnect delay on successful connection + self._last_ping = time.time() + retry_count = 0 # Reset retry count on successful connection + self._authenticated = False # Reset auth status on new connection + + except (asyncio.TimeoutError, websockets.exceptions.WebSocketException) as e: + next_retry = self._reconnect_delay + + # Only log the first error at WARNING level, then every Nth attempt + if retry_count == 1: + self.logger.warning( + f"Computer API Server not ready yet. Will retry automatically." + ) + elif retry_count % log_interval == 0: + self.logger.warning( + f"Still waiting for Computer API Server (attempt {retry_count})..." + ) + else: + # All other errors are logged at DEBUG level + self.logger.debug(f"Connection attempt {retry_count} failed: {e}") + + if self._ws: + try: + await self._ws.close() + except: + pass + self._ws = None + + # Regular ping to check connection + if self._ws and self._ws.state == websockets.protocol.State.OPEN: + try: + if time.time() - self._last_ping >= self._ping_interval: + pong_waiter = await self._ws.ping() + await asyncio.wait_for(pong_waiter, timeout=self._ping_timeout) + self._last_ping = time.time() + except Exception as e: + self.logger.debug(f"Ping failed: {e}") + if self._ws: + try: + await self._ws.close() + except: + pass + self._ws = None + continue + + await asyncio.sleep(1) + + except Exception as e: + current_time = time.time() + # Only log connection lost warnings at most once every min_warning_interval seconds + if current_time - last_warning_time >= min_warning_interval: + self.logger.warning( + f"Computer API Server connection lost. Will retry automatically." + ) + last_warning_time = current_time + else: + # Log at debug level instead + self.logger.debug(f"Connection lost: {e}") + + if self._ws: + try: + await self._ws.close() + except: + pass + self._ws = None + + async def _ensure_connection(self): + """Ensure WebSocket connection is established.""" + if self._reconnect_task is None or self._reconnect_task.done(): + self._reconnect_task = asyncio.create_task(self._keep_alive()) + + retry_count = 0 + max_retries = 5 + + while retry_count < max_retries: + try: + if self._ws and self._ws.state == websockets.protocol.State.OPEN: + return + retry_count += 1 + await asyncio.sleep(1) + except Exception as e: + # Only log at ERROR level for the last retry attempt + if retry_count == max_retries - 1: + self.logger.error( + f"Persistent connection check error after {retry_count} attempts: {e}" + ) + else: + self.logger.debug(f"Connection check error (attempt {retry_count}): {e}") + retry_count += 1 + await asyncio.sleep(1) + continue + + raise ConnectionError("Failed to establish WebSocket connection after multiple retries") + + async def _send_command(self, command: str, params: Optional[Dict] = None) -> Dict[str, Any]: + """Send command through WebSocket.""" + max_retries = 3 + retry_count = 0 + last_error = None + + # Acquire lock to ensure only one command is processed at a time + async with self._command_lock: + self.logger.debug(f"Acquired lock for command: {command}") + while retry_count < max_retries: + try: + await self._ensure_connection() + if not self._ws: + raise ConnectionError("WebSocket connection is not established") + + # Handle authentication if needed + if self.api_key and self.vm_name and not self._authenticated: + self.logger.info("Performing authentication handshake...") + auth_message = { + "command": "authenticate", + "params": { + "api_key": self.api_key, + "container_name": self.vm_name + } + } + await self._ws.send(json.dumps(auth_message)) + + # Wait for authentication response + auth_response = await asyncio.wait_for(self._ws.recv(), timeout=10) + auth_result = json.loads(auth_response) + + if not auth_result.get("success"): + error_msg = auth_result.get("error", "Authentication failed") + self.logger.error(f"Authentication failed: {error_msg}") + self._authenticated = False + raise ConnectionError(f"Authentication failed: {error_msg}") + + self.logger.info("Authentication successful") + self._authenticated = True + + message = {"command": command, "params": params or {}} + await self._ws.send(json.dumps(message)) + response = await asyncio.wait_for(self._ws.recv(), timeout=30) + self.logger.debug(f"Completed command: {command}") + return json.loads(response) + except Exception as e: + last_error = e + retry_count += 1 + if retry_count < max_retries: + # Only log at debug level for intermediate retries + self.logger.debug( + f"Command '{command}' failed (attempt {retry_count}/{max_retries}): {e}" + ) + await asyncio.sleep(1) + continue + else: + # Only log at error level for the final failure + self.logger.error( + f"Failed to send command '{command}' after {max_retries} retries" + ) + self.logger.debug(f"Command failure details: {e}") + raise last_error if last_error else RuntimeError("Failed to send command") + + async def wait_for_ready(self, timeout: int = 60, interval: float = 1.0): + """Wait for WebSocket connection to become available.""" + start_time = time.time() + last_error = None + attempt_count = 0 + progress_interval = 10 # Log progress every 10 seconds + last_progress_time = start_time + + # Disable detailed logging for connection attempts + self._log_connection_attempts = False + + try: + self.logger.info( + f"Waiting for Computer API Server to be ready (timeout: {timeout}s)..." + ) + + # Start the keep-alive task if it's not already running + if self._reconnect_task is None or self._reconnect_task.done(): + self._reconnect_task = asyncio.create_task(self._keep_alive()) + + # Wait for the connection to be established + while time.time() - start_time < timeout: + try: + attempt_count += 1 + current_time = time.time() + + # Log progress periodically without flooding logs + if current_time - last_progress_time >= progress_interval: + elapsed = current_time - start_time + self.logger.info( + f"Still waiting for Computer API Server... (elapsed: {elapsed:.1f}s, attempts: {attempt_count})" + ) + last_progress_time = current_time + + # Check if we have a connection + if self._ws and self._ws.state == websockets.protocol.State.OPEN: + # Test the connection with a simple command + try: + await self._send_command("get_screen_size") + elapsed = time.time() - start_time + self.logger.info( + f"Computer API Server is ready (after {elapsed:.1f}s, {attempt_count} attempts)" + ) + return # Connection is fully working + except Exception as e: + last_error = e + self.logger.debug(f"Connection test failed: {e}") + + # Wait before trying again + await asyncio.sleep(interval) + + except Exception as e: + last_error = e + self.logger.debug(f"Connection attempt {attempt_count} failed: {e}") + await asyncio.sleep(interval) + + # If we get here, we've timed out + error_msg = f"Could not connect to {self.ip_address} after {timeout} seconds" + if last_error: + error_msg += f": {str(last_error)}" + self.logger.error(error_msg) + raise TimeoutError(error_msg) + finally: + # Reset to default logging behavior + self._log_connection_attempts = False + + def close(self): + """Close WebSocket connection. + + Note: In host computer server mode, we leave the connection open + to allow other clients to connect to the same server. The server + will handle cleaning up idle connections. + """ + # Only cancel the reconnect task + if self._reconnect_task: + self._reconnect_task.cancel() + + # Don't set closed flag or close websocket by default + # This allows the server to stay connected for other clients + # self._closed = True + # if self._ws: + # asyncio.create_task(self._ws.close()) + # self._ws = None + + def force_close(self): + """Force close the WebSocket connection. + + This method should be called when you want to completely + shut down the connection, not just for regular cleanup. + """ + self._closed = True + if self._reconnect_task: + self._reconnect_task.cancel() + if self._ws: + asyncio.create_task(self._ws.close()) + self._ws = None + + # Mouse Actions + async def mouse_down(self, x: Optional[int] = None, y: Optional[int] = None, button: str = "left") -> None: + await self._send_command("mouse_down", {"x": x, "y": y, "button": button}) + + async def mouse_up(self, x: Optional[int] = None, y: Optional[int] = None, button: str = "left") -> None: + await self._send_command("mouse_up", {"x": x, "y": y, "button": button}) + + async def left_click(self, x: Optional[int] = None, y: Optional[int] = None) -> None: + await self._send_command("left_click", {"x": x, "y": y}) + + async def right_click(self, x: Optional[int] = None, y: Optional[int] = None) -> None: + await self._send_command("right_click", {"x": x, "y": y}) + + async def double_click(self, x: Optional[int] = None, y: Optional[int] = None) -> None: + await self._send_command("double_click", {"x": x, "y": y}) + + async def move_cursor(self, x: int, y: int) -> None: + await self._send_command("move_cursor", {"x": x, "y": y}) + + async def drag_to(self, x: int, y: int, button: "MouseButton" = "left", duration: float = 0.5) -> None: + await self._send_command( + "drag_to", {"x": x, "y": y, "button": button, "duration": duration} + ) + + async def drag(self, path: List[Tuple[int, int]], button: "MouseButton" = "left", duration: float = 0.5) -> None: + await self._send_command( + "drag", {"path": path, "button": button, "duration": duration} + ) + + # Keyboard Actions + async def key_down(self, key: "KeyType") -> None: + await self._send_command("key_down", {"key": key}) + + async def key_up(self, key: "KeyType") -> None: + await self._send_command("key_up", {"key": key}) + + async def type_text(self, text: str) -> None: + # For Windows, use clipboard for Unicode text like Linux + if any(ord(char) > 127 for char in text): + # For Unicode text, use clipboard and paste + await self.set_clipboard(text) + await self.hotkey(Key.CTRL, 'v') # Windows uses Ctrl+V instead of Cmd+V + else: + # For ASCII text, use the regular typing method + await self._send_command("type_text", {"text": text}) + + async def press(self, key: "KeyType") -> None: + """Press a single key. + + Args: + key: The key to press. Can be any of: + - A Key enum value (recommended), e.g. Key.PAGE_DOWN + - A direct key value string, e.g. 'pagedown' + - A single character string, e.g. 'a' + + Examples: + ```python + # Using enum (recommended) + await interface.press(Key.PAGE_DOWN) + await interface.press(Key.ENTER) + + # Using direct values + await interface.press('pagedown') + await interface.press('enter') + + # Using single characters + await interface.press('a') + ``` + + Raises: + ValueError: If the key type is invalid or the key is not recognized + """ + if isinstance(key, Key): + actual_key = key.value + elif isinstance(key, str): + # Try to convert to enum if it matches a known key + key_or_enum = Key.from_string(key) + actual_key = key_or_enum.value if isinstance(key_or_enum, Key) else key_or_enum + else: + raise ValueError(f"Invalid key type: {type(key)}. Must be Key enum or string.") + + await self._send_command("press_key", {"key": actual_key}) + + async def press_key(self, key: "KeyType") -> None: + """DEPRECATED: Use press() instead. + + This method is kept for backward compatibility but will be removed in a future version. + Please use the press() method instead. + """ + await self.press(key) + + async def hotkey(self, *keys: "KeyType") -> None: + """Press multiple keys simultaneously. + + Args: + *keys: Multiple keys to press simultaneously. Each key can be any of: + - A Key enum value (recommended), e.g. Key.CTRL + - A direct key value string, e.g. 'ctrl' + - A single character string, e.g. 'a' + + Examples: + ```python + # Using enums (recommended) + await interface.hotkey(Key.CTRL, Key.C) # Copy + await interface.hotkey(Key.CTRL, Key.V) # Paste + + # Using mixed formats + await interface.hotkey(Key.CTRL, 'a') # Select all + ``` + + Raises: + ValueError: If any key type is invalid or not recognized + """ + actual_keys = [] + for key in keys: + if isinstance(key, Key): + actual_keys.append(key.value) + elif isinstance(key, str): + # Try to convert to enum if it matches a known key + key_or_enum = Key.from_string(key) + actual_keys.append(key_or_enum.value if isinstance(key_or_enum, Key) else key_or_enum) + else: + raise ValueError(f"Invalid key type: {type(key)}. Must be Key enum or string.") + + await self._send_command("hotkey", {"keys": actual_keys}) + + # Scrolling Actions + async def scroll(self, x: int, y: int) -> None: + await self._send_command("scroll", {"x": x, "y": y}) + + async def scroll_down(self, clicks: int = 1) -> None: + await self._send_command("scroll_down", {"clicks": clicks}) + + async def scroll_up(self, clicks: int = 1) -> None: + await self._send_command("scroll_up", {"clicks": clicks}) + + # Screen Actions + async def screenshot( + self, + boxes: Optional[List[Tuple[int, int, int, int]]] = None, + box_color: str = "#FF0000", + box_thickness: int = 2, + scale_factor: float = 1.0, + ) -> bytes: + """Take a screenshot with optional box drawing and scaling. + + Args: + boxes: Optional list of (x, y, width, height) tuples defining boxes to draw in screen coordinates + box_color: Color of the boxes in hex format (default: "#FF0000" red) + box_thickness: Thickness of the box borders in pixels (default: 2) + scale_factor: Factor to scale the final image by (default: 1.0) + Use > 1.0 to enlarge, < 1.0 to shrink (e.g., 0.5 for half size, 2.0 for double) + + Returns: + bytes: The screenshot image data, optionally with boxes drawn on it and scaled + """ + result = await self._send_command("screenshot") + if not result.get("image_data"): + raise RuntimeError("Failed to take screenshot") + + screenshot = decode_base64_image(result["image_data"]) + + if boxes: + # Get the natural scaling between screen and screenshot + screen_size = await self.get_screen_size() + screenshot_width, screenshot_height = bytes_to_image(screenshot).size + width_scale = screenshot_width / screen_size["width"] + height_scale = screenshot_height / screen_size["height"] + + # Scale box coordinates from screen space to screenshot space + for box in boxes: + scaled_box = ( + int(box[0] * width_scale), # x + int(box[1] * height_scale), # y + int(box[2] * width_scale), # width + int(box[3] * height_scale), # height + ) + screenshot = draw_box( + screenshot, + x=scaled_box[0], + y=scaled_box[1], + width=scaled_box[2], + height=scaled_box[3], + color=box_color, + thickness=box_thickness, + ) + + if scale_factor != 1.0: + screenshot = resize_image(screenshot, scale_factor) + + return screenshot + + async def get_screen_size(self) -> Dict[str, int]: + result = await self._send_command("get_screen_size") + if result["success"] and result["size"]: + return result["size"] + raise RuntimeError("Failed to get screen size") + + async def get_cursor_position(self) -> Dict[str, int]: + result = await self._send_command("get_cursor_position") + if result["success"] and result["position"]: + return result["position"] + raise RuntimeError("Failed to get cursor position") + + # Clipboard Actions + async def copy_to_clipboard(self) -> str: + result = await self._send_command("copy_to_clipboard") + if result["success"] and result["content"]: + return result["content"] + raise RuntimeError("Failed to get clipboard content") + + async def set_clipboard(self, text: str) -> None: + await self._send_command("set_clipboard", {"text": text}) + + # File System Actions + async def file_exists(self, path: str) -> bool: + result = await self._send_command("file_exists", {"path": path}) + return result.get("exists", False) + + async def directory_exists(self, path: str) -> bool: + result = await self._send_command("directory_exists", {"path": path}) + return result.get("exists", False) + + async def list_dir(self, path: str) -> list[str]: + result = await self._send_command("list_dir", {"path": path}) + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to list directory")) + return result.get("files", []) + + async def read_text(self, path: str) -> str: + result = await self._send_command("read_text", {"path": path}) + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to read file")) + return result.get("content", "") + + async def write_text(self, path: str, content: str) -> None: + result = await self._send_command("write_text", {"path": path, "content": content}) + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to write file")) + + async def read_bytes(self, path: str) -> bytes: + result = await self._send_command("read_bytes", {"path": path}) + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to read file")) + content_b64 = result.get("content_b64", "") + return decode_base64_image(content_b64) + + async def write_bytes(self, path: str, content: bytes) -> None: + result = await self._send_command("write_bytes", {"path": path, "content_b64": encode_base64_image(content)}) + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to write file")) + + async def delete_file(self, path: str) -> None: + result = await self._send_command("delete_file", {"path": path}) + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to delete file")) + + async def create_dir(self, path: str) -> None: + result = await self._send_command("create_dir", {"path": path}) + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to create directory")) + + async def delete_dir(self, path: str) -> None: + result = await self._send_command("delete_dir", {"path": path}) + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to delete directory")) + + async def run_command(self, command: str) -> Tuple[str, str]: + result = await self._send_command("run_command", {"command": command}) + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to run command")) + return result.get("stdout", ""), result.get("stderr", "") + + # Accessibility Actions + async def get_accessibility_tree(self) -> Dict[str, Any]: + """Get the accessibility tree of the current screen.""" + result = await self._send_command("get_accessibility_tree") + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to get accessibility tree")) + return result + + async def get_active_window_bounds(self) -> Dict[str, int]: + """Get the bounds of the currently active window.""" + result = await self._send_command("get_active_window_bounds") + if result["success"] and result["bounds"]: + return result["bounds"] + raise RuntimeError("Failed to get active window bounds") + + async def to_screen_coordinates(self, x: float, y: float) -> tuple[float, float]: + """Convert screenshot coordinates to screen coordinates. + + Args: + x: X coordinate in screenshot space + y: Y coordinate in screenshot space + + Returns: + tuple[float, float]: (x, y) coordinates in screen space + """ + screen_size = await self.get_screen_size() + screenshot = await self.screenshot() + screenshot_img = bytes_to_image(screenshot) + screenshot_width, screenshot_height = screenshot_img.size + + # Calculate scaling factors + width_scale = screen_size["width"] / screenshot_width + height_scale = screen_size["height"] / screenshot_height + + # Convert coordinates + screen_x = x * width_scale + screen_y = y * height_scale + + return screen_x, screen_y + + async def to_screenshot_coordinates(self, x: float, y: float) -> tuple[float, float]: + """Convert screen coordinates to screenshot coordinates. + + Args: + x: X coordinate in screen space + y: Y coordinate in screen space + + Returns: + tuple[float, float]: (x, y) coordinates in screenshot space + """ + screen_size = await self.get_screen_size() + screenshot = await self.screenshot() + screenshot_img = bytes_to_image(screenshot) + screenshot_width, screenshot_height = screenshot_img.size + + # Calculate scaling factors + width_scale = screenshot_width / screen_size["width"] + height_scale = screenshot_height / screen_size["height"] + + # Convert coordinates + screenshot_x = x * width_scale + screenshot_y = y * height_scale + + return screenshot_x, screenshot_y From fc413338948703b412f000c033897fdec6735fe3 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Fri, 13 Jun 2025 12:48:28 -0400 Subject: [PATCH 036/141] More refactoring --- .../typescript/src/computer/computer.ts | 2 +- libs/computer/typescript/src/index.ts | 18 ++++++++++-------- libs/computer/typescript/src/interface/base.ts | 14 +++++++------- .../computer/typescript/src/interface/index.ts | 2 +- .../computer/typescript/src/interface/linux.ts | 10 +++++----- .../computer/typescript/src/interface/macos.ts | 10 +++++----- .../src/interface/{models.ts => types.ts} | 0 libs/computer/typescript/src/logger.ts | 0 libs/computer/typescript/src/telemetry.ts | 2 ++ .../typescript/src/{models.ts => types.ts} | 0 10 files changed, 31 insertions(+), 27 deletions(-) rename libs/computer/typescript/src/interface/{models.ts => types.ts} (100%) delete mode 100644 libs/computer/typescript/src/logger.ts rename libs/computer/typescript/src/{models.ts => types.ts} (100%) diff --git a/libs/computer/typescript/src/computer/computer.ts b/libs/computer/typescript/src/computer/computer.ts index fea5cbb3..eea9e563 100644 --- a/libs/computer/typescript/src/computer/computer.ts +++ b/libs/computer/typescript/src/computer/computer.ts @@ -1,4 +1,4 @@ -import type { Display, ComputerConfig } from "../models"; +import type { Display, ComputerConfig } from "../types"; import type { BaseComputerInterface } from "../interface/base"; import { InterfaceFactory } from "../interface/factory"; import type { BaseVMProvider } from "../providers/base"; diff --git a/libs/computer/typescript/src/index.ts b/libs/computer/typescript/src/index.ts index 551181ca..79eec2b5 100644 --- a/libs/computer/typescript/src/index.ts +++ b/libs/computer/typescript/src/index.ts @@ -3,7 +3,7 @@ export { Computer } from "./computer"; export type { ComputerOptions, OSType } from "./computer"; // Models -export type { Display, ComputerConfig } from "./models"; +export type { Display, ComputerConfig } from "./types"; // Provider components export { VMProviderType, BaseVMProviderImpl } from "./providers"; @@ -16,13 +16,15 @@ export type { BaseComputerInterface } from "./interface"; export { InterfaceFactory } from "./interface"; export type { InterfaceOptions } from "./interface"; export { Key } from "./interface"; -export type { - KeyType, - MouseButton, - NavigationKey, - SpecialKey, - ModifierKey, +export type { + KeyType, + MouseButton, + NavigationKey, + SpecialKey, + ModifierKey, FunctionKey, AccessibilityWindow, - AccessibilityTree + AccessibilityTree, } from "./interface"; + +export * from "./helpers"; diff --git a/libs/computer/typescript/src/interface/base.ts b/libs/computer/typescript/src/interface/base.ts index ef4dfe6c..9ab3fd0e 100644 --- a/libs/computer/typescript/src/interface/base.ts +++ b/libs/computer/typescript/src/interface/base.ts @@ -1,4 +1,4 @@ -import type { KeyType, MouseButton, AccessibilityTree } from './models'; +import type { KeyType, MouseButton, AccessibilityTree } from "./types"; /** * Base interface for computer control implementations. @@ -8,32 +8,32 @@ export interface BaseComputerInterface { * Wait for the interface to be ready. */ waitForReady(): Promise; - + /** * Get a screenshot of the current screen. */ getScreenshot(): Promise; - + /** * Move the mouse to the specified coordinates. */ moveMouse(x: number, y: number): Promise; - + /** * Click the mouse at the current position. */ click(button?: MouseButton): Promise; - + /** * Type text at the current cursor position. */ typeText(text: string): Promise; - + /** * Press a key. */ pressKey(key: KeyType): Promise; - + /** * Get the accessibility tree. */ diff --git a/libs/computer/typescript/src/interface/index.ts b/libs/computer/typescript/src/interface/index.ts index ac96f84e..38e3fd04 100644 --- a/libs/computer/typescript/src/interface/index.ts +++ b/libs/computer/typescript/src/interface/index.ts @@ -1,5 +1,5 @@ export * from "./base"; export * from "./factory"; -export * from "./models"; +export * from "./types"; export * from "./macos"; export * from "./linux"; diff --git a/libs/computer/typescript/src/interface/linux.ts b/libs/computer/typescript/src/interface/linux.ts index 3713cb7b..12e67988 100644 --- a/libs/computer/typescript/src/interface/linux.ts +++ b/libs/computer/typescript/src/interface/linux.ts @@ -1,5 +1,5 @@ -import type { BaseComputerInterface } from './base'; -import type { KeyType, MouseButton, AccessibilityTree } from './models'; +import type { BaseComputerInterface } from "./base"; +import type { KeyType, MouseButton, AccessibilityTree } from "./types"; /** * Linux-specific implementation of the computer interface. @@ -24,7 +24,7 @@ export class LinuxComputerInterface implements BaseComputerInterface { // Implementation will go here } - async click(button: MouseButton = 'left'): Promise { + async click(button: MouseButton = "left"): Promise { // Implementation will go here } @@ -40,8 +40,8 @@ export class LinuxComputerInterface implements BaseComputerInterface { // Implementation will go here return { success: false, - frontmost_application: '', - windows: [] + frontmost_application: "", + windows: [], }; } } diff --git a/libs/computer/typescript/src/interface/macos.ts b/libs/computer/typescript/src/interface/macos.ts index a031fa49..93e2688d 100644 --- a/libs/computer/typescript/src/interface/macos.ts +++ b/libs/computer/typescript/src/interface/macos.ts @@ -1,5 +1,5 @@ -import type { BaseComputerInterface } from './base'; -import type { KeyType, MouseButton, AccessibilityTree } from './models'; +import type { BaseComputerInterface } from "./base"; +import type { KeyType, MouseButton, AccessibilityTree } from "./types"; /** * macOS-specific implementation of the computer interface. @@ -24,7 +24,7 @@ export class MacOSComputerInterface implements BaseComputerInterface { // Implementation will go here } - async click(button: MouseButton = 'left'): Promise { + async click(button: MouseButton = "left"): Promise { // Implementation will go here } @@ -40,8 +40,8 @@ export class MacOSComputerInterface implements BaseComputerInterface { // Implementation will go here return { success: false, - frontmost_application: '', - windows: [] + frontmost_application: "", + windows: [], }; } } diff --git a/libs/computer/typescript/src/interface/models.ts b/libs/computer/typescript/src/interface/types.ts similarity index 100% rename from libs/computer/typescript/src/interface/models.ts rename to libs/computer/typescript/src/interface/types.ts diff --git a/libs/computer/typescript/src/logger.ts b/libs/computer/typescript/src/logger.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/libs/computer/typescript/src/telemetry.ts b/libs/computer/typescript/src/telemetry.ts index 7f598721..2cd193de 100644 --- a/libs/computer/typescript/src/telemetry.ts +++ b/libs/computer/typescript/src/telemetry.ts @@ -37,6 +37,8 @@ export class TelemetryManager { // For now, just log to debug if (process.env.NODE_ENV === "development") { console.debug("[Telemetry]", event, properties); + } else { + //todo: log telemetry to posthog } } diff --git a/libs/computer/typescript/src/models.ts b/libs/computer/typescript/src/types.ts similarity index 100% rename from libs/computer/typescript/src/models.ts rename to libs/computer/typescript/src/types.ts From 9aa273e939e8d0f484788ee41c842d0489faafea Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Fri, 13 Jun 2025 15:39:14 -0400 Subject: [PATCH 037/141] added winsandbox --- libs/computer/computer/computer.py | 19 +- libs/computer/computer/providers/base.py | 1 + libs/computer/computer/providers/factory.py | 22 ++ .../providers/winsandbox/logon_script.bat | 10 + .../computer/providers/winsandbox/provider.py | 350 ++++++++++++++++++ libs/computer/computer/ui/gradio/app.py | 2 +- 6 files changed, 402 insertions(+), 2 deletions(-) create mode 100644 libs/computer/computer/providers/winsandbox/logon_script.bat create mode 100644 libs/computer/computer/providers/winsandbox/provider.py diff --git a/libs/computer/computer/computer.py b/libs/computer/computer/computer.py index 4249f3f4..1d5d261e 100644 --- a/libs/computer/computer/computer.py +++ b/libs/computer/computer/computer.py @@ -106,7 +106,15 @@ class Computer: # The default is currently to use non-ephemeral storage if storage and ephemeral and storage != "ephemeral": raise ValueError("Storage path and ephemeral flag cannot be used together") - self.storage = "ephemeral" if ephemeral else storage + + # Windows Sandbox always uses ephemeral storage + if self.provider_type == VMProviderType.WINSANDBOX: + if not ephemeral: + self.logger.warning("Windows Sandbox storage is always ephemeral. Setting ephemeral=True.") + self.ephemeral = True + self.storage = "ephemeral" + else: + self.storage = "ephemeral" if ephemeral else storage # For Lumier provider, store the first shared directory path to use # for VM file sharing @@ -285,6 +293,15 @@ class Computer: api_key=self.api_key, verbose=verbose, ) + elif self.provider_type == VMProviderType.WINSANDBOX: + self.config.vm_provider = VMProviderFactory.create_provider( + self.provider_type, + port=port, + host=host, + storage=storage, + verbose=verbose, + ephemeral=ephemeral, + ) else: raise ValueError(f"Unsupported provider type: {self.provider_type}") self._provider_context = await self.config.vm_provider.__aenter__() diff --git a/libs/computer/computer/providers/base.py b/libs/computer/computer/providers/base.py index 4a8f8fdf..a3540e0e 100644 --- a/libs/computer/computer/providers/base.py +++ b/libs/computer/computer/providers/base.py @@ -10,6 +10,7 @@ class VMProviderType(StrEnum): LUME = "lume" LUMIER = "lumier" CLOUD = "cloud" + WINSANDBOX = "winsandbox" UNKNOWN = "unknown" diff --git a/libs/computer/computer/providers/factory.py b/libs/computer/computer/providers/factory.py index 6491b754..98fcd9da 100644 --- a/libs/computer/computer/providers/factory.py +++ b/libs/computer/computer/providers/factory.py @@ -112,5 +112,27 @@ class VMProviderFactory: "The CloudProvider is not fully implemented yet. " "Please use LUME or LUMIER provider instead." ) from e + elif provider_type == VMProviderType.WINSANDBOX: + try: + from .winsandbox import WinSandboxProvider, HAS_WINSANDBOX + if not HAS_WINSANDBOX: + raise ImportError( + "pywinsandbox is required for WinSandboxProvider. " + "Please install it with 'pip install -U git+https://github.com/karkason/pywinsandbox.git'" + ) + return WinSandboxProvider( + port=port, + host=host, + storage=storage, + verbose=verbose, + ephemeral=ephemeral, + **kwargs + ) + except ImportError as e: + logger.error(f"Failed to import WinSandboxProvider: {e}") + raise ImportError( + "pywinsandbox is required for WinSandboxProvider. " + "Please install it with 'pip install -U git+https://github.com/karkason/pywinsandbox.git'" + ) from e else: raise ValueError(f"Unsupported provider type: {provider_type}") diff --git a/libs/computer/computer/providers/winsandbox/logon_script.bat b/libs/computer/computer/providers/winsandbox/logon_script.bat new file mode 100644 index 00000000..f5bd58c1 --- /dev/null +++ b/libs/computer/computer/providers/winsandbox/logon_script.bat @@ -0,0 +1,10 @@ +@echo off +REM Logon script for Windows Sandbox CUA Computer provider +REM This script runs when the sandbox starts + +REM Open explorer to show the desktop +explorer . + +REM TODO: Install CUA computer server +REM pip install cua-computer-server +REM python -m computer_server.main --ws diff --git a/libs/computer/computer/providers/winsandbox/provider.py b/libs/computer/computer/providers/winsandbox/provider.py new file mode 100644 index 00000000..29c52330 --- /dev/null +++ b/libs/computer/computer/providers/winsandbox/provider.py @@ -0,0 +1,350 @@ +"""Windows Sandbox VM provider implementation using pywinsandbox.""" + +import os +import asyncio +import logging +import time +from typing import Dict, Any, Optional, List + +from ..base import BaseVMProvider, VMProviderType + +# Setup logging +logger = logging.getLogger(__name__) + +try: + import winsandbox + HAS_WINSANDBOX = True +except ImportError: + HAS_WINSANDBOX = False + + +class WinSandboxProvider(BaseVMProvider): + """Windows Sandbox VM provider implementation using pywinsandbox. + + This provider uses Windows Sandbox to create isolated Windows environments. + Storage is always ephemeral with Windows Sandbox. + """ + + def __init__( + self, + port: int = 7777, + host: str = "localhost", + storage: Optional[str] = None, + verbose: bool = False, + ephemeral: bool = True, # Windows Sandbox is always ephemeral + memory_mb: int = 4096, + networking: bool = True, + **kwargs + ): + """Initialize the Windows Sandbox provider. + + Args: + port: Port for the computer server (default: 7777) + host: Host to use for connections (default: localhost) + storage: Storage path (ignored - Windows Sandbox is always ephemeral) + verbose: Enable verbose logging + ephemeral: Always True for Windows Sandbox + memory_mb: Memory allocation in MB (default: 4096) + networking: Enable networking in sandbox (default: True) + """ + if not HAS_WINSANDBOX: + raise ImportError( + "pywinsandbox is required for WinSandboxProvider. " + "Please install it with 'pip install pywinsandbox'" + ) + + self.host = host + self.port = port + self.verbose = verbose + self.memory_mb = memory_mb + self.networking = networking + + # Windows Sandbox is always ephemeral + if not ephemeral: + logger.warning("Windows Sandbox storage is always ephemeral. Ignoring ephemeral=False.") + self.ephemeral = True + + # Storage is always ephemeral for Windows Sandbox + if storage and storage != "ephemeral": + logger.warning("Windows Sandbox does not support persistent storage. Using ephemeral storage.") + self.storage = "ephemeral" + + self.logger = logging.getLogger(__name__) + + # Track active sandboxes + self._active_sandboxes: Dict[str, Any] = {} + + @property + def provider_type(self) -> VMProviderType: + """Get the provider type.""" + return VMProviderType.WINSANDBOX + + async def __aenter__(self): + """Enter async context manager.""" + # Verify Windows Sandbox is available + if not HAS_WINSANDBOX: + raise ImportError("pywinsandbox is not available") + + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + """Exit async context manager.""" + # Clean up any active sandboxes + for name, sandbox in self._active_sandboxes.items(): + try: + sandbox.shutdown() + self.logger.info(f"Terminated sandbox: {name}") + except Exception as e: + self.logger.error(f"Error terminating sandbox {name}: {e}") + + self._active_sandboxes.clear() + + async def get_vm(self, name: str, storage: Optional[str] = None) -> Dict[str, Any]: + """Get VM information by name. + + Args: + name: Name of the VM to get information for + storage: Ignored for Windows Sandbox (always ephemeral) + + Returns: + Dictionary with VM information including status, IP address, etc. + """ + if name not in self._active_sandboxes: + return { + "name": name, + "status": "stopped", + "ip_address": None, + "storage": "ephemeral" + } + + sandbox = self._active_sandboxes[name] + + # Check if sandbox is still running + try: + # For Windows Sandbox, we assume it's running if it's in our active list + # and hasn't been terminated + # Try to ping the sandbox to see if it's responsive + try: + # Simple test to see if RPyC connection is alive + sandbox.rpyc.modules.os.getcwd() + status = "running" + # Windows Sandbox typically uses localhost for RPyC connections + ip_address = "127.0.0.1" + except Exception: + status = "starting" + ip_address = None + except Exception as e: + self.logger.error(f"Error checking sandbox status: {e}") + status = "error" + ip_address = None + + return { + "name": name, + "status": status, + "ip_address": ip_address, + "storage": "ephemeral", + "memory_mb": self.memory_mb, + "networking": self.networking + } + + async def list_vms(self) -> List[Dict[str, Any]]: + """List all available VMs.""" + vms = [] + for name in self._active_sandboxes.keys(): + vm_info = await self.get_vm(name) + vms.append(vm_info) + return vms + + async def run_vm(self, image: str, name: str, run_opts: Dict[str, Any], storage: Optional[str] = None) -> Dict[str, Any]: + """Run a VM with the given options. + + Args: + image: Image name (ignored for Windows Sandbox - always uses host Windows) + name: Name of the VM to run + run_opts: Dictionary of run options (memory, cpu, etc.) + storage: Ignored for Windows Sandbox (always ephemeral) + + Returns: + Dictionary with VM run status and information + """ + if name in self._active_sandboxes: + return { + "success": False, + "error": f"Sandbox {name} is already running" + } + + try: + # Extract options from run_opts + memory_mb = run_opts.get("memory_mb", self.memory_mb) + if isinstance(memory_mb, str): + # Convert memory string like "4GB" to MB + if memory_mb.upper().endswith("GB"): + memory_mb = int(float(memory_mb[:-2]) * 1024) + elif memory_mb.upper().endswith("MB"): + memory_mb = int(memory_mb[:-2]) + else: + memory_mb = self.memory_mb + + networking = run_opts.get("networking", self.networking) + + # Get the logon script path + script_path = os.path.join(os.path.dirname(__file__), "logon_script.bat") + + # Create folder mappers if shared directories are specified + folder_mappers = [] + shared_directories = run_opts.get("shared_directories", []) + for shared_dir in shared_directories: + if isinstance(shared_dir, dict): + host_path = shared_dir.get("hostPath", "") + elif isinstance(shared_dir, str): + host_path = shared_dir + else: + continue + + if host_path and os.path.exists(host_path): + folder_mappers.append(winsandbox.FolderMapper(host_path)) + + self.logger.info(f"Creating Windows Sandbox: {name}") + self.logger.info(f"Memory: {memory_mb}MB, Networking: {networking}") + if folder_mappers: + self.logger.info(f"Shared directories: {len(folder_mappers)}") + + # Create the sandbox + sandbox = winsandbox.new_sandbox( + memory_mb=str(memory_mb), + networking=networking, + logon_script=f'cmd /c "{script_path}"', + folder_mappers=folder_mappers + ) + + # Store the sandbox + self._active_sandboxes[name] = sandbox + + self.logger.info(f"Windows Sandbox {name} created successfully") + + return { + "success": True, + "name": name, + "status": "starting", + "memory_mb": memory_mb, + "networking": networking, + "storage": "ephemeral" + } + + except Exception as e: + self.logger.error(f"Failed to create Windows Sandbox {name}: {e}") + return { + "success": False, + "error": f"Failed to create sandbox: {str(e)}" + } + + async def stop_vm(self, name: str, storage: Optional[str] = None) -> Dict[str, Any]: + """Stop a running VM. + + Args: + name: Name of the VM to stop + storage: Ignored for Windows Sandbox + + Returns: + Dictionary with stop status and information + """ + if name not in self._active_sandboxes: + return { + "success": False, + "error": f"Sandbox {name} is not running" + } + + try: + sandbox = self._active_sandboxes[name] + + # Terminate the sandbox + sandbox.shutdown() + + # Remove from active sandboxes + del self._active_sandboxes[name] + + self.logger.info(f"Windows Sandbox {name} stopped successfully") + + return { + "success": True, + "name": name, + "status": "stopped" + } + + except Exception as e: + self.logger.error(f"Failed to stop Windows Sandbox {name}: {e}") + return { + "success": False, + "error": f"Failed to stop sandbox: {str(e)}" + } + + async def update_vm(self, name: str, update_opts: Dict[str, Any], storage: Optional[str] = None) -> Dict[str, Any]: + """Update VM configuration. + + Note: Windows Sandbox does not support runtime configuration updates. + The sandbox must be stopped and restarted with new configuration. + + Args: + name: Name of the VM to update + update_opts: Dictionary of update options + storage: Ignored for Windows Sandbox + + Returns: + Dictionary with update status and information + """ + return { + "success": False, + "error": "Windows Sandbox does not support runtime configuration updates. " + "Please stop and restart the sandbox with new configuration." + } + + async def get_ip(self, name: str, storage: Optional[str] = None, retry_delay: int = 2) -> str: + """Get the IP address of a VM, waiting indefinitely until it's available. + + Args: + name: Name of the VM to get the IP for + storage: Ignored for Windows Sandbox + retry_delay: Delay between retries in seconds (default: 2) + + Returns: + IP address of the VM when it becomes available + """ + total_attempts = 0 + + # Loop indefinitely until we get a valid IP + while True: + total_attempts += 1 + + # Log retry message but not on first attempt + if total_attempts > 1: + self.logger.info(f"Waiting for Windows Sandbox {name} IP address (attempt {total_attempts})...") + + try: + # Get VM information + vm_info = await self.get_vm(name, storage=storage) + + # Check if we got a valid IP + ip = vm_info.get("ip_address", None) + if ip and ip != "unknown" and not ip.startswith("0.0.0.0"): + self.logger.info(f"Got valid Windows Sandbox IP address: {ip}") + return ip + + # Check the VM status + status = vm_info.get("status", "unknown") + + # If VM is not running yet, log and wait + if status != "running": + self.logger.info(f"Windows Sandbox is not running yet (status: {status}). Waiting...") + # If VM is running but no IP yet, wait and retry + else: + self.logger.info("Windows Sandbox is running but no valid IP address yet. Waiting...") + + except Exception as e: + self.logger.warning(f"Error getting Windows Sandbox {name} IP: {e}, continuing to wait...") + + # Wait before next retry + await asyncio.sleep(retry_delay) + + # Add progress log every 10 attempts + if total_attempts % 10 == 0: + self.logger.info(f"Still waiting for Windows Sandbox {name} IP after {total_attempts} attempts...") diff --git a/libs/computer/computer/ui/gradio/app.py b/libs/computer/computer/ui/gradio/app.py index 7825de3f..3808fa89 100644 --- a/libs/computer/computer/ui/gradio/app.py +++ b/libs/computer/computer/ui/gradio/app.py @@ -1070,7 +1070,7 @@ def create_gradio_ui(): label="OS", choices=["macOS", "Ubuntu", "Windows"], value="macOS", - interactive=False # disable until the ubuntu image is ready + # interactive=False # disable until the ubuntu image is ready ) # Provider selection radio From 7d267701a4bfcd3f09be3e75d861c2cc86c2241e Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Sat, 14 Jun 2025 10:05:12 -0400 Subject: [PATCH 038/141] Removed pre/post action screenshots in tools. Improved image retention (#289) --- libs/agent/agent/core/messages.py | 47 ++++- .../providers/anthropic/tools/computer.py | 174 +----------------- .../agent/providers/openai/tools/computer.py | 50 +---- 3 files changed, 57 insertions(+), 214 deletions(-) diff --git a/libs/agent/agent/core/messages.py b/libs/agent/agent/core/messages.py index 2a582a7a..d2c70558 100644 --- a/libs/agent/agent/core/messages.py +++ b/libs/agent/agent/core/messages.py @@ -81,16 +81,27 @@ class StandardMessageManager: if not self.config.num_images_to_keep: return messages - # Find user messages with images + # Find messages with images (both user messages and tool call outputs) image_messages = [] for msg in messages: + has_image = False + + # Check user messages with images if msg["role"] == "user" and isinstance(msg["content"], list): has_image = any( item.get("type") == "image_url" or item.get("type") == "image" for item in msg["content"] ) - if has_image: - image_messages.append(msg) + + # Check assistant messages with tool calls that have images + elif msg["role"] == "assistant" and isinstance(msg["content"], list): + for item in msg["content"]: + if item.get("type") == "tool_result" and "base64_image" in item: + has_image = True + break + + if has_image: + image_messages.append(msg) # If we don't have more images than the limit, return all messages if len(image_messages) <= self.config.num_images_to_keep: @@ -100,13 +111,35 @@ class StandardMessageManager: images_to_keep = image_messages[-self.config.num_images_to_keep :] images_to_remove = image_messages[: -self.config.num_images_to_keep] - # Create a new message list without the older images + # Create a new message list, removing images from older messages result = [] for msg in messages: if msg in images_to_remove: - # Skip this message - continue - result.append(msg) + # Remove images from this message but keep the text content + if msg["role"] == "user" and isinstance(msg["content"], list): + # Keep only text content, remove images + new_content = [ + item for item in msg["content"] + if item.get("type") not in ["image_url", "image"] + ] + if new_content: # Only add if there's still content + result.append({"role": msg["role"], "content": new_content}) + elif msg["role"] == "assistant" and isinstance(msg["content"], list): + # Remove base64_image from tool_result items + new_content = [] + for item in msg["content"]: + if item.get("type") == "tool_result" and "base64_image" in item: + # Create a copy without the base64_image + new_item = {k: v for k, v in item.items() if k != "base64_image"} + new_content.append(new_item) + else: + new_content.append(item) + result.append({"role": msg["role"], "content": new_content}) + else: + # For other message types, keep as is + result.append(msg) + else: + result.append(msg) return result diff --git a/libs/agent/agent/providers/anthropic/tools/computer.py b/libs/agent/agent/providers/anthropic/tools/computer.py index 2bb944ea..dd1dc281 100644 --- a/libs/agent/agent/providers/anthropic/tools/computer.py +++ b/libs/agent/agent/providers/anthropic/tools/computer.py @@ -205,26 +205,6 @@ class ComputerTool(BaseComputerTool, BaseAnthropicTool): self.logger.info(f" Coordinates: ({x}, {y})") try: - # Take pre-action screenshot to get current dimensions - pre_screenshot = await self.computer.interface.screenshot() - pre_img = Image.open(io.BytesIO(pre_screenshot)) - - # Scale image to match screen dimensions if needed - if pre_img.size != (self.width, self.height): - self.logger.info( - f"Scaling image from {pre_img.size} to {self.width}x{self.height} to match screen dimensions" - ) - if not isinstance(self.width, int) or not isinstance(self.height, int): - raise ToolError("Screen dimensions must be integers") - size = (int(self.width), int(self.height)) - pre_img = pre_img.resize(size, Image.Resampling.LANCZOS) - # Save the scaled image back to bytes - buffer = io.BytesIO() - pre_img.save(buffer, format="PNG") - pre_screenshot = buffer.getvalue() - - self.logger.info(f" Current dimensions: {pre_img.width}x{pre_img.height}") - # Perform the click action if action == "left_click": self.logger.info(f"Clicking at ({x}, {y})") @@ -242,45 +222,14 @@ class ComputerTool(BaseComputerTool, BaseAnthropicTool): # Wait briefly for any UI changes await asyncio.sleep(0.5) - # Take and save post-action screenshot - post_screenshot = await self.computer.interface.screenshot() - post_img = Image.open(io.BytesIO(post_screenshot)) - - # Scale post-action image if needed - if post_img.size != (self.width, self.height): - self.logger.info( - f"Scaling post-action image from {post_img.size} to {self.width}x{self.height}" - ) - post_img = post_img.resize( - (self.width, self.height), Image.Resampling.LANCZOS - ) - buffer = io.BytesIO() - post_img.save(buffer, format="PNG") - post_screenshot = buffer.getvalue() - return ToolResult( output=f"Performed {action} at ({x}, {y})", - base64_image=base64.b64encode(post_screenshot).decode(), ) except Exception as e: self.logger.error(f"Error during {action} action: {str(e)}") raise ToolError(f"Failed to perform {action}: {str(e)}") else: try: - # Take pre-action screenshot - pre_screenshot = await self.computer.interface.screenshot() - pre_img = Image.open(io.BytesIO(pre_screenshot)) - - # Scale image if needed - if pre_img.size != (self.width, self.height): - self.logger.info( - f"Scaling image from {pre_img.size} to {self.width}x{self.height}" - ) - if not isinstance(self.width, int) or not isinstance(self.height, int): - raise ToolError("Screen dimensions must be integers") - size = (int(self.width), int(self.height)) - pre_img = pre_img.resize(size, Image.Resampling.LANCZOS) - # Perform the click action if action == "left_click": self.logger.info("Performing left click at current position") @@ -295,25 +244,8 @@ class ComputerTool(BaseComputerTool, BaseAnthropicTool): # Wait briefly for any UI changes await asyncio.sleep(0.5) - # Take post-action screenshot - post_screenshot = await self.computer.interface.screenshot() - post_img = Image.open(io.BytesIO(post_screenshot)) - - # Scale post-action image if needed - if post_img.size != (self.width, self.height): - self.logger.info( - f"Scaling post-action image from {post_img.size} to {self.width}x{self.height}" - ) - post_img = post_img.resize( - (self.width, self.height), Image.Resampling.LANCZOS - ) - buffer = io.BytesIO() - post_img.save(buffer, format="PNG") - post_screenshot = buffer.getvalue() - return ToolResult( output=f"Performed {action} at current position", - base64_image=base64.b64encode(post_screenshot).decode(), ) except Exception as e: self.logger.error(f"Error during {action} action: {str(e)}") @@ -328,20 +260,6 @@ class ComputerTool(BaseComputerTool, BaseAnthropicTool): raise ToolError(f"{text} must be a string") try: - # Take pre-action screenshot - pre_screenshot = await self.computer.interface.screenshot() - pre_img = Image.open(io.BytesIO(pre_screenshot)) - - # Scale image if needed - if pre_img.size != (self.width, self.height): - self.logger.info( - f"Scaling image from {pre_img.size} to {self.width}x{self.height}" - ) - if not isinstance(self.width, int) or not isinstance(self.height, int): - raise ToolError("Screen dimensions must be integers") - size = (int(self.width), int(self.height)) - pre_img = pre_img.resize(size, Image.Resampling.LANCZOS) - if action == "key": # Special handling for page up/down on macOS if text.lower() in ["pagedown", "page_down", "page down"]: @@ -378,25 +296,8 @@ class ComputerTool(BaseComputerTool, BaseAnthropicTool): # Wait briefly for UI changes await asyncio.sleep(0.5) - # Take post-action screenshot - post_screenshot = await self.computer.interface.screenshot() - post_img = Image.open(io.BytesIO(post_screenshot)) - - # Scale post-action image if needed - if post_img.size != (self.width, self.height): - self.logger.info( - f"Scaling post-action image from {post_img.size} to {self.width}x{self.height}" - ) - post_img = post_img.resize( - (self.width, self.height), Image.Resampling.LANCZOS - ) - buffer = io.BytesIO() - post_img.save(buffer, format="PNG") - post_screenshot = buffer.getvalue() - return ToolResult( output=f"Pressed key: {output_text}", - base64_image=base64.b64encode(post_screenshot).decode(), ) elif action == "type": @@ -406,66 +307,13 @@ class ComputerTool(BaseComputerTool, BaseAnthropicTool): # Wait briefly for UI changes await asyncio.sleep(0.5) - # Take post-action screenshot - post_screenshot = await self.computer.interface.screenshot() - post_img = Image.open(io.BytesIO(post_screenshot)) - - # Scale post-action image if needed - if post_img.size != (self.width, self.height): - self.logger.info( - f"Scaling post-action image from {post_img.size} to {self.width}x{self.height}" - ) - post_img = post_img.resize( - (self.width, self.height), Image.Resampling.LANCZOS - ) - buffer = io.BytesIO() - post_img.save(buffer, format="PNG") - post_screenshot = buffer.getvalue() - return ToolResult( output=f"Typed text: {text}", - base64_image=base64.b64encode(post_screenshot).decode(), ) except Exception as e: self.logger.error(f"Error during {action} action: {str(e)}") raise ToolError(f"Failed to perform {action}: {str(e)}") - elif action in ("screenshot", "cursor_position"): - if text is not None: - raise ToolError(f"text is not accepted for {action}") - if coordinate is not None: - raise ToolError(f"coordinate is not accepted for {action}") - - try: - if action == "screenshot": - # Take screenshot - screenshot = await self.computer.interface.screenshot() - img = Image.open(io.BytesIO(screenshot)) - - # Scale image if needed - if img.size != (self.width, self.height): - self.logger.info( - f"Scaling image from {img.size} to {self.width}x{self.height}" - ) - if not isinstance(self.width, int) or not isinstance(self.height, int): - raise ToolError("Screen dimensions must be integers") - size = (int(self.width), int(self.height)) - img = img.resize(size, Image.Resampling.LANCZOS) - buffer = io.BytesIO() - img.save(buffer, format="PNG") - screenshot = buffer.getvalue() - - return ToolResult(base64_image=base64.b64encode(screenshot).decode()) - - elif action == "cursor_position": - pos = await self.computer.interface.get_cursor_position() - x, y = pos # Unpack the tuple - return ToolResult(output=f"X={int(x)},Y={int(y)}") - - except Exception as e: - self.logger.error(f"Error during {action} action: {str(e)}") - raise ToolError(f"Failed to perform {action}: {str(e)}") - elif action == "scroll": # Implement scroll action direction = kwargs.get("direction", "down") @@ -487,28 +335,20 @@ class ComputerTool(BaseComputerTool, BaseAnthropicTool): # Wait briefly for UI changes await asyncio.sleep(0.5) - # Take post-action screenshot - post_screenshot = await self.computer.interface.screenshot() - post_img = Image.open(io.BytesIO(post_screenshot)) - - # Scale post-action image if needed - if post_img.size != (self.width, self.height): - self.logger.info( - f"Scaling post-action image from {post_img.size} to {self.width}x{self.height}" - ) - post_img = post_img.resize((self.width, self.height), Image.Resampling.LANCZOS) - buffer = io.BytesIO() - post_img.save(buffer, format="PNG") - post_screenshot = buffer.getvalue() - return ToolResult( output=f"Scrolled {direction} by {amount} steps", - base64_image=base64.b64encode(post_screenshot).decode(), ) except Exception as e: self.logger.error(f"Error during scroll action: {str(e)}") raise ToolError(f"Failed to perform scroll: {str(e)}") + elif action == "screenshot": + # Take screenshot + return await self.screenshot() + elif action == "cursor_position": + pos = await self.computer.interface.get_cursor_position() + x, y = pos # Unpack the tuple + return ToolResult(output=f"X={int(x)},Y={int(y)}") raise ToolError(f"Invalid action: {action}") async def screenshot(self): diff --git a/libs/agent/agent/providers/openai/tools/computer.py b/libs/agent/agent/providers/openai/tools/computer.py index c5602f4e..5575c792 100644 --- a/libs/agent/agent/providers/openai/tools/computer.py +++ b/libs/agent/agent/providers/openai/tools/computer.py @@ -61,9 +61,6 @@ class ComputerTool(BaseComputerTool, BaseOpenAITool): computer: Computer # The CUA Computer instance logger = logging.getLogger(__name__) - _screenshot_delay = 1.0 # macOS is generally faster than X11 - _scaling_enabled = True - def __init__(self, computer: Computer): """Initialize the computer tool. @@ -185,26 +182,23 @@ class ComputerTool(BaseComputerTool, BaseOpenAITool): raise ToolError(f"Failed to execute {type}: {str(e)}") async def handle_click(self, button: str, x: int, y: int) -> ToolResult: - """Handle different click actions.""" + """Handle mouse clicks.""" try: - # Perform requested click action + # Perform the click based on button type if button == "left": await self.computer.interface.left_click(x, y) elif button == "right": await self.computer.interface.right_click(x, y) elif button == "double": await self.computer.interface.double_click(x, y) + else: + raise ToolError(f"Unsupported button type: {button}") - # Wait for UI to update - await asyncio.sleep(0.5) - - # Take screenshot after action - screenshot = await self.computer.interface.screenshot() - base64_screenshot = base64.b64encode(screenshot).decode("utf-8") + # Wait briefly for UI to update + await asyncio.sleep(0.3) return ToolResult( output=f"Performed {button} click at ({x}, {y})", - base64_image=base64_screenshot, ) except Exception as e: self.logger.error(f"Error in handle_click: {str(e)}") @@ -218,11 +212,7 @@ class ComputerTool(BaseComputerTool, BaseOpenAITool): await asyncio.sleep(0.3) - # Take screenshot after typing - screenshot = await self.computer.interface.screenshot() - base64_screenshot = base64.b64encode(screenshot).decode("utf-8") - - return ToolResult(output=f"Typed: {text}", base64_image=base64_screenshot) + return ToolResult(output=f"Typed: {text}") except Exception as e: self.logger.error(f"Error in handle_typing: {str(e)}") raise ToolError(f"Failed to type '{text}': {str(e)}") @@ -254,11 +244,7 @@ class ComputerTool(BaseComputerTool, BaseOpenAITool): # Wait briefly await asyncio.sleep(0.3) - # Take screenshot after action - screenshot = await self.computer.interface.screenshot() - base64_screenshot = base64.b64encode(screenshot).decode("utf-8") - - return ToolResult(output=f"Pressed key: {key}", base64_image=base64_screenshot) + return ToolResult(output=f"Pressed key: {key}") except Exception as e: self.logger.error(f"Error in handle_key: {str(e)}") raise ToolError(f"Failed to press key '{key}': {str(e)}") @@ -272,11 +258,7 @@ class ComputerTool(BaseComputerTool, BaseOpenAITool): # Wait briefly await asyncio.sleep(0.2) - # Take screenshot after action - screenshot = await self.computer.interface.screenshot() - base64_screenshot = base64.b64encode(screenshot).decode("utf-8") - - return ToolResult(output=f"Moved cursor to ({x}, {y})", base64_image=base64_screenshot) + return ToolResult(output=f"Moved cursor to ({x}, {y})") except Exception as e: self.logger.error(f"Error in handle_mouse_move: {str(e)}") raise ToolError(f"Failed to move cursor to ({x}, {y}): {str(e)}") @@ -296,14 +278,7 @@ class ComputerTool(BaseComputerTool, BaseOpenAITool): # Wait for UI to update await asyncio.sleep(0.5) - # Take screenshot after action - screenshot = await self.computer.interface.screenshot() - base64_screenshot = base64.b64encode(screenshot).decode("utf-8") - - return ToolResult( - output=f"Scrolled at ({x}, {y}) with delta ({scroll_x}, {scroll_y})", - base64_image=base64_screenshot, - ) + return ToolResult(output=f"Scrolled at ({x}, {y}) by ({scroll_x}, {scroll_y})") except Exception as e: self.logger.error(f"Error in handle_scroll: {str(e)}") raise ToolError(f"Failed to scroll at ({x}, {y}): {str(e)}") @@ -331,13 +306,8 @@ class ComputerTool(BaseComputerTool, BaseOpenAITool): # Wait for UI to update await asyncio.sleep(0.5) - # Take screenshot after action - screenshot = await self.computer.interface.screenshot() - base64_screenshot = base64.b64encode(screenshot).decode("utf-8") - return ToolResult( output=f"Dragged from ({path[0]['x']}, {path[0]['y']}) to ({path[-1]['x']}, {path[-1]['y']})", - base64_image=base64_screenshot, ) except Exception as e: self.logger.error(f"Error in handle_drag: {str(e)}") From 22b8a236d632d8edc97331e9a4c7e04e3c425b0b Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Sat, 14 Jun 2025 10:52:05 -0400 Subject: [PATCH 039/141] added cloud provider to computer UI --- libs/computer/computer/ui/gradio/app.py | 64 ++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 6 deletions(-) diff --git a/libs/computer/computer/ui/gradio/app.py b/libs/computer/computer/ui/gradio/app.py index b1d131d9..8dc77786 100644 --- a/libs/computer/computer/ui/gradio/app.py +++ b/libs/computer/computer/ui/gradio/app.py @@ -528,13 +528,15 @@ async def execute(name, action, arguments): return results -async def handle_init_computer(os_choice: str, app_list=None, provider="lume"): +async def handle_init_computer(os_choice: str, app_list=None, provider="lume", container_name=None, api_key=None): """Initialize the computer instance and tools for macOS or Ubuntu Args: os_choice: The OS to use ("macOS" or "Ubuntu") app_list: Optional list of apps to focus on using the app-use experiment - provider: The provider to use ("lume" or "self") + provider: The provider to use ("lume" or "self" or "cloud") + container_name: The container name to use for cloud provider + api_key: The API key to use for cloud provider """ global computer, tool_call_logs, tools @@ -559,6 +561,16 @@ async def handle_init_computer(os_choice: str, app_list=None, provider="lume"): use_host_computer_server=True, experiments=experiments ) + elif provider == "cloud": + # Use API key from environment variable or field input + cloud_api_key = os.environ.get("CUA_API_KEY") or api_key + computer = Computer( + os_type=os_type_str, + provider_type=VMProviderType.CLOUD, + name=container_name, + api_key=cloud_api_key, + experiments=experiments + ) else: computer = Computer( image=image_str, @@ -596,6 +608,10 @@ async def handle_init_computer(os_choice: str, app_list=None, provider="lume"): init_params["apps"] = app_list init_params["experiments"] = ["app-use"] + # Add container name to the log if using cloud provider + if provider == "cloud": + init_params["container_name"] = container_name + result = await execute("computer", "initialize", init_params) return result["screenshot"], json.dumps(tool_call_logs, indent=2) @@ -1073,11 +1089,31 @@ def create_gradio_ui(): # Provider selection radio provider_choice = gr.Radio( label="Provider", - choices=["lume", "self"], + choices=["lume", "self", "cloud"], value="lume", - info="'lume' uses a VM, 'self' uses the host computer server" + info="'lume' uses a VM, 'self' uses the host computer server, 'cloud' uses a cloud container" ) + # Container name field for cloud provider (initially hidden) + container_name = gr.Textbox( + label="Container Name", + placeholder="Enter your container name", + visible=False, + info="Get your container from [trycua.com](https://trycua.com/)" + ) + + # Check if CUA_API_KEY is set in environment + has_cua_key = os.environ.get("CUA_API_KEY") is not None + + # API key field for cloud provider (visible only if no env key and cloud selected) + api_key_field = gr.Textbox( + label="CUA API Key", + placeholder="Enter your CUA API key", + type="password", + visible=False, + info="Required for cloud provider. Set CUA_API_KEY environment variable to hide this field." + ) + # App filtering dropdown for app-use experiment app_filter = gr.Dropdown( label="Filter by apps (App-Use)", @@ -1085,6 +1121,22 @@ def create_gradio_ui(): allow_custom_value=True, info="When apps are selected, the computer will focus on those apps using the app-use experiment" ) + + # Function to show/hide container name and API key fields based on provider selection + def update_cloud_fields_visibility(provider): + show_container = provider == "cloud" + show_api_key = provider == "cloud" and not has_cua_key + return ( + gr.update(visible=show_container), + gr.update(visible=show_api_key) + ) + + # Connect provider choice to field visibility + provider_choice.change( + update_cloud_fields_visibility, + inputs=provider_choice, + outputs=[container_name, api_key_field] + ) start_btn = gr.Button("Initialize Computer") @@ -1149,7 +1201,7 @@ def create_gradio_ui(): value=False ) message_submit_btn = gr.Button("Submit Message") - message_status = gr.Textbox(label="Status", value="") + message_status = gr.Textbox(label="Status") with gr.Accordion("Clipboard Operations", open=False): clipboard_content = gr.Textbox(label="Clipboard Content") @@ -1250,7 +1302,7 @@ def create_gradio_ui(): ) img.select(handle_click, inputs=[img, click_type], outputs=[img, action_log]) - start_btn.click(handle_init_computer, inputs=[os_choice, app_filter, provider_choice], outputs=[img, action_log]) + start_btn.click(handle_init_computer, inputs=[os_choice, app_filter, provider_choice, container_name, api_key_field], outputs=[img, action_log]) wait_btn.click(handle_wait, outputs=[img, action_log]) # DONE and FAIL buttons just do a placeholder action From ca9308fbd223704087b8f943307cf54946a09d87 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Sat, 14 Jun 2025 11:15:53 -0400 Subject: [PATCH 040/141] Enabled os_choice --- libs/computer/computer/ui/gradio/app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/computer/computer/ui/gradio/app.py b/libs/computer/computer/ui/gradio/app.py index 8dc77786..a9ae2154 100644 --- a/libs/computer/computer/ui/gradio/app.py +++ b/libs/computer/computer/ui/gradio/app.py @@ -1083,7 +1083,6 @@ def create_gradio_ui(): label="OS", choices=["macOS", "Ubuntu"], value="macOS", - interactive=False # disable until the ubuntu image is ready ) # Provider selection radio From 52fc5dd56391a55e4dcdcd64b8cff0ad6381c290 Mon Sep 17 00:00:00 2001 From: f-trycua Date: Mon, 16 Jun 2025 17:12:08 -0700 Subject: [PATCH 041/141] Add proper exit codes when notarization fails --- .../scripts/build/build-release-notarized.sh | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/libs/lume/scripts/build/build-release-notarized.sh b/libs/lume/scripts/build/build-release-notarized.sh index 19fb2e88..018570b2 100755 --- a/libs/lume/scripts/build/build-release-notarized.sh +++ b/libs/lume/scripts/build/build-release-notarized.sh @@ -89,24 +89,33 @@ if [ "$LOG_LEVEL" = "minimal" ] || [ "$LOG_LEVEL" = "none" ]; then --password "${APP_SPECIFIC_PASSWORD}" \ --wait 2>&1) - # Just show success or failure + # Check if notarization was successful if echo "$NOTARY_OUTPUT" | grep -q "status: Accepted"; then log "essential" "Notarization successful!" else log "error" "Notarization failed. Please check logs." + log "error" "Notarization output:" + echo "$NOTARY_OUTPUT" + exit 1 fi else # Normal verbose output - xcrun notarytool submit ./.release/lume.pkg \ + if ! xcrun notarytool submit ./.release/lume.pkg \ --apple-id "${APPLE_ID}" \ --team-id "${TEAM_ID}" \ --password "${APP_SPECIFIC_PASSWORD}" \ - --wait + --wait; then + log "error" "Notarization failed" + exit 1 + fi fi # Staple the notarization ticket log "essential" "Stapling notarization ticket..." -xcrun stapler staple ./.release/lume.pkg > /dev/null 2>&1 +if ! xcrun stapler staple ./.release/lume.pkg > /dev/null 2>&1; then + log "error" "Failed to staple notarization ticket" + exit 1 +fi # Create temporary directory for package extraction EXTRACT_ROOT=$(mktemp -d) From dad7d4c30393b0b522f10a38d924f210fb674fa9 Mon Sep 17 00:00:00 2001 From: f-trycua Date: Mon, 16 Jun 2025 17:19:46 -0700 Subject: [PATCH 042/141] Fix package build error handling and certificate verification - Add proper error checking for pkgbuild command - Verify package exists before attempting notarization - Improve certificate verification in GitHub workflow - Show actual certificate details instead of just count - Add specific checks for required Developer ID certificates This should fix the 'file doesn't exist' error during notarization. --- .github/workflows/publish-lume.yml | 19 ++++++++++++++++--- .../scripts/build/build-release-notarized.sh | 15 +++++++++++++-- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/.github/workflows/publish-lume.yml b/.github/workflows/publish-lume.yml index 3b2311ee..d90df18e 100644 --- a/.github/workflows/publish-lume.yml +++ b/.github/workflows/publish-lume.yml @@ -114,9 +114,22 @@ jobs: # Allow codesign to access the certificates (minimal output) security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain > /dev/null 2>&1 - # Verify certificates were imported but only show count, not details - echo "Verifying signing identity (showing count only)..." - security find-identity -v -p codesigning | grep -c "valid identities found" || true + # Verify certificates were imported + echo "Verifying signing identities..." + security find-identity -v -p codesigning build.keychain + + # Verify specific certificates exist + if ! security find-identity -v -p codesigning build.keychain | grep -q "Developer ID Application: ${{ secrets.DEVELOPER_NAME }}"; then + echo "Error: Developer ID Application certificate not found" + exit 1 + fi + + if ! security find-identity -v -p codesigning build.keychain | grep -q "Developer ID Installer: ${{ secrets.DEVELOPER_NAME }}"; then + echo "Error: Developer ID Installer certificate not found" + exit 1 + fi + + echo "All required certificates verified successfully" # Clean up certificate files rm application.p12 installer.p12 diff --git a/libs/lume/scripts/build/build-release-notarized.sh b/libs/lume/scripts/build/build-release-notarized.sh index 018570b2..603446b7 100755 --- a/libs/lume/scripts/build/build-release-notarized.sh +++ b/libs/lume/scripts/build/build-release-notarized.sh @@ -72,12 +72,23 @@ cp -f .build/release/lume "$TEMP_ROOT/usr/local/bin/" # Build the installer package log "essential" "Building installer package..." -pkgbuild --root "$TEMP_ROOT" \ +if ! pkgbuild --root "$TEMP_ROOT" \ --identifier "com.trycua.lume" \ --version "1.0" \ --install-location "/" \ --sign "$CERT_INSTALLER_NAME" \ - ./.release/lume.pkg 2> /dev/null + ./.release/lume.pkg; then + log "error" "Failed to build installer package" + exit 1 +fi + +# Verify the package was created +if [ ! -f "./.release/lume.pkg" ]; then + log "error" "Package file ./.release/lume.pkg was not created" + exit 1 +fi + +log "essential" "Package created successfully" # Submit for notarization using stored credentials log "essential" "Submitting for notarization..." From 8ccee43460d2df2da13f044d94bf9d245c6974d8 Mon Sep 17 00:00:00 2001 From: f-trycua Date: Mon, 16 Jun 2025 17:45:33 -0700 Subject: [PATCH 043/141] Fix certificate verification to handle GitHub secret masking --- .github/workflows/publish-lume.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/publish-lume.yml b/.github/workflows/publish-lume.yml index d90df18e..ec5e7550 100644 --- a/.github/workflows/publish-lume.yml +++ b/.github/workflows/publish-lume.yml @@ -116,19 +116,22 @@ jobs: # Verify certificates were imported echo "Verifying signing identities..." - security find-identity -v -p codesigning build.keychain + CERT_COUNT=$(security find-identity -v -p codesigning build.keychain | grep -c "Developer ID Application" || echo "0") + INSTALLER_COUNT=$(security find-identity -v build.keychain | grep -c "Developer ID Installer" || echo "0") - # Verify specific certificates exist - if ! security find-identity -v -p codesigning build.keychain | grep -q "Developer ID Application: ${{ secrets.DEVELOPER_NAME }}"; then - echo "Error: Developer ID Application certificate not found" + if [ "$CERT_COUNT" -eq 0 ]; then + echo "Error: No Developer ID Application certificate found" + security find-identity -v -p codesigning build.keychain exit 1 fi - if ! security find-identity -v -p codesigning build.keychain | grep -q "Developer ID Installer: ${{ secrets.DEVELOPER_NAME }}"; then - echo "Error: Developer ID Installer certificate not found" + if [ "$INSTALLER_COUNT" -eq 0 ]; then + echo "Error: No Developer ID Installer certificate found" + security find-identity -v build.keychain exit 1 fi + echo "Found $CERT_COUNT Developer ID Application certificate(s) and $INSTALLER_COUNT Developer ID Installer certificate(s)" echo "All required certificates verified successfully" # Clean up certificate files From f12be458e2f34312d7f5f9b83020f32a866c614e Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 17 Jun 2025 12:22:36 -0400 Subject: [PATCH 044/141] added sandbox provider --- libs/computer/computer/computer.py | 5 +- .../providers/winsandbox/logon_script.bat | 10 -- .../computer/providers/winsandbox/provider.py | 142 ++++++++++++++++-- .../providers/winsandbox/setup_script.ps1 | 124 +++++++++++++++ 4 files changed, 257 insertions(+), 24 deletions(-) delete mode 100644 libs/computer/computer/providers/winsandbox/logon_script.bat create mode 100644 libs/computer/computer/providers/winsandbox/setup_script.ps1 diff --git a/libs/computer/computer/computer.py b/libs/computer/computer/computer.py index 1d5d261e..8596bc8b 100644 --- a/libs/computer/computer/computer.py +++ b/libs/computer/computer/computer.py @@ -109,7 +109,7 @@ class Computer: # Windows Sandbox always uses ephemeral storage if self.provider_type == VMProviderType.WINSANDBOX: - if not ephemeral: + if not ephemeral and storage != None and storage != "ephemeral": self.logger.warning("Windows Sandbox storage is always ephemeral. Setting ephemeral=True.") self.ephemeral = True self.storage = "ephemeral" @@ -400,7 +400,6 @@ class Computer: # Wait for VM to be ready with a valid IP address self.logger.info("Waiting for VM to be ready with a valid IP address...") try: - # Increased values for Lumier provider which needs more time for initial setup if self.provider_type == VMProviderType.LUMIER: max_retries = 60 # Increased for Lumier VM startup which takes longer retry_delay = 3 # 3 seconds between retries for Lumier @@ -530,7 +529,7 @@ class Computer: return # @property - async def get_ip(self, max_retries: int = 15, retry_delay: int = 2) -> str: + async def get_ip(self, max_retries: int = 15, retry_delay: int = 3) -> str: """Get the IP address of the VM or localhost if using host computer server. This method delegates to the provider's get_ip method, which waits indefinitely diff --git a/libs/computer/computer/providers/winsandbox/logon_script.bat b/libs/computer/computer/providers/winsandbox/logon_script.bat deleted file mode 100644 index f5bd58c1..00000000 --- a/libs/computer/computer/providers/winsandbox/logon_script.bat +++ /dev/null @@ -1,10 +0,0 @@ -@echo off -REM Logon script for Windows Sandbox CUA Computer provider -REM This script runs when the sandbox starts - -REM Open explorer to show the desktop -explorer . - -REM TODO: Install CUA computer server -REM pip install cua-computer-server -REM python -m computer_server.main --ws diff --git a/libs/computer/computer/providers/winsandbox/provider.py b/libs/computer/computer/providers/winsandbox/provider.py index 29c52330..53d0148e 100644 --- a/libs/computer/computer/providers/winsandbox/provider.py +++ b/libs/computer/computer/providers/winsandbox/provider.py @@ -121,18 +121,73 @@ class WinSandboxProvider(BaseVMProvider): # Check if sandbox is still running try: - # For Windows Sandbox, we assume it's running if it's in our active list - # and hasn't been terminated # Try to ping the sandbox to see if it's responsive try: - # Simple test to see if RPyC connection is alive sandbox.rpyc.modules.os.getcwd() - status = "running" - # Windows Sandbox typically uses localhost for RPyC connections - ip_address = "127.0.0.1" + sandbox_responsive = True except Exception: + sandbox_responsive = False + + if not sandbox_responsive: + return { + "name": name, + "status": "starting", + "ip_address": None, + "storage": "ephemeral", + "memory_mb": self.memory_mb, + "networking": self.networking + } + + # Check for computer server address file + server_address_file = r"C:\Users\WDAGUtilityAccount\Desktop\shared_windows_sandbox_dir\server_address" + + try: + # Check if the server address file exists + file_exists = sandbox.rpyc.modules.os.path.exists(server_address_file) + + if file_exists: + # Read the server address file + with sandbox.rpyc.builtin.open(server_address_file, 'r') as f: + server_address = f.read().strip() + + if server_address and ':' in server_address: + # Parse IP:port from the file + ip_address, port = server_address.split(':', 1) + + # Verify the server is actually responding + try: + import socket + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(3) + result = sock.connect_ex((ip_address, int(port))) + sock.close() + + if result == 0: + # Server is responding + status = "running" + self.logger.debug(f"Computer server found at {ip_address}:{port}") + else: + # Server file exists but not responding + status = "starting" + ip_address = None + except Exception as e: + self.logger.debug(f"Error checking server connectivity: {e}") + status = "starting" + ip_address = None + else: + # File exists but doesn't contain valid address + status = "starting" + ip_address = None + else: + # Server address file doesn't exist yet + status = "starting" + ip_address = None + + except Exception as e: + self.logger.debug(f"Error checking server address file: {e}") status = "starting" ip_address = None + except Exception as e: self.logger.error(f"Error checking sandbox status: {e}") status = "error" @@ -187,9 +242,6 @@ class WinSandboxProvider(BaseVMProvider): networking = run_opts.get("networking", self.networking) - # Get the logon script path - script_path = os.path.join(os.path.dirname(__file__), "logon_script.bat") - # Create folder mappers if shared directories are specified folder_mappers = [] shared_directories = run_opts.get("shared_directories", []) @@ -209,11 +261,10 @@ class WinSandboxProvider(BaseVMProvider): if folder_mappers: self.logger.info(f"Shared directories: {len(folder_mappers)}") - # Create the sandbox + # Create the sandbox without logon script sandbox = winsandbox.new_sandbox( memory_mb=str(memory_mb), networking=networking, - logon_script=f'cmd /c "{script_path}"', folder_mappers=folder_mappers ) @@ -222,6 +273,9 @@ class WinSandboxProvider(BaseVMProvider): self.logger.info(f"Windows Sandbox {name} created successfully") + # Setup the computer server in the sandbox + await self._setup_computer_server(sandbox, name) + return { "success": True, "name": name, @@ -233,6 +287,9 @@ class WinSandboxProvider(BaseVMProvider): except Exception as e: self.logger.error(f"Failed to create Windows Sandbox {name}: {e}") + # stack trace + import traceback + self.logger.error(f"Stack trace: {traceback.format_exc()}") return { "success": False, "error": f"Failed to create sandbox: {str(e)}" @@ -348,3 +405,66 @@ class WinSandboxProvider(BaseVMProvider): # Add progress log every 10 attempts if total_attempts % 10 == 0: self.logger.info(f"Still waiting for Windows Sandbox {name} IP after {total_attempts} attempts...") + + async def _setup_computer_server(self, sandbox, name: str, visible: bool = False): + """Setup the computer server in the Windows Sandbox using RPyC. + + Args: + sandbox: The Windows Sandbox instance + name: Name of the sandbox + visible: Whether the opened process should be visible (default: False) + """ + try: + self.logger.info(f"Setting up computer server in sandbox {name}...") + print(f"Setting up computer server in sandbox {name}...") + + # Read the PowerShell setup script + script_path = os.path.join(os.path.dirname(__file__), "setup_script.ps1") + with open(script_path, 'r', encoding='utf-8') as f: + setup_script_content = f.read() + + # Write the setup script to the sandbox using RPyC + script_dest_path = r"C:\Users\WDAGUtilityAccount\setup_cua.ps1" + + print(f"Writing setup script to {script_dest_path}") + with sandbox.rpyc.builtin.open(script_dest_path, 'w') as f: + f.write(setup_script_content) + + # Execute the PowerShell script in the background + print("Executing setup script in sandbox...") + + # Use subprocess to run PowerShell script + import subprocess + powershell_cmd = [ + "powershell.exe", + "-ExecutionPolicy", "Bypass", + "-NoExit", # Keep window open after script completes + "-File", script_dest_path + ] + + # Set creation flags based on visibility preference + if visible: + # CREATE_NEW_CONSOLE - creates a new console window (visible) + creation_flags = 0x00000010 + else: + # DETACHED_PROCESS - runs in background (not visible) + creation_flags = 0x00000008 + + # Start the process using RPyC + process = sandbox.rpyc.modules.subprocess.Popen( + powershell_cmd, + creationflags=creation_flags, + shell=False + ) + + # Sleep for 30 seconds + await asyncio.sleep(30) + + ip = await self.get_ip(name) + print(f"Sandbox IP: {ip}") + print(f"Setup script started in background in sandbox {name} with PID: {process.pid}") + + except Exception as e: + self.logger.error(f"Failed to setup computer server in sandbox {name}: {e}") + import traceback + self.logger.error(f"Stack trace: {traceback.format_exc()}") diff --git a/libs/computer/computer/providers/winsandbox/setup_script.ps1 b/libs/computer/computer/providers/winsandbox/setup_script.ps1 new file mode 100644 index 00000000..73074764 --- /dev/null +++ b/libs/computer/computer/providers/winsandbox/setup_script.ps1 @@ -0,0 +1,124 @@ +# Setup script for Windows Sandbox CUA Computer provider +# This script runs when the sandbox starts + +Write-Host "Starting CUA Computer setup in Windows Sandbox..." + +# Function to find the mapped Python installation from pywinsandbox +function Find-MappedPython { + Write-Host "Looking for mapped Python installation from pywinsandbox..." + + # pywinsandbox maps the host Python installation to the sandbox + # Look for mapped shared folders on the desktop (common pywinsandbox pattern) + $desktopPath = "C:\Users\WDAGUtilityAccount\Desktop" + $sharedFolders = Get-ChildItem -Path $desktopPath -Directory -ErrorAction SilentlyContinue + + foreach ($folder in $sharedFolders) { + # Look for Python executables in shared folders + $pythonPaths = @( + "$($folder.FullName)\python.exe", + "$($folder.FullName)\Scripts\python.exe", + "$($folder.FullName)\bin\python.exe" + ) + + foreach ($pythonPath in $pythonPaths) { + if (Test-Path $pythonPath) { + try { + $version = & $pythonPath --version 2>&1 + if ($version -match "Python") { + Write-Host "Found mapped Python: $pythonPath - $version" + return $pythonPath + } + } catch { + continue + } + } + } + + # Also check subdirectories that might contain Python + $subDirs = Get-ChildItem -Path $folder.FullName -Directory -ErrorAction SilentlyContinue + foreach ($subDir in $subDirs) { + $pythonPath = "$($subDir.FullName)\python.exe" + if (Test-Path $pythonPath) { + try { + $version = & $pythonPath --version 2>&1 + if ($version -match "Python") { + Write-Host "Found mapped Python in subdirectory: $pythonPath - $version" + return $pythonPath + } + } catch { + continue + } + } + } + } + + # Fallback: try common Python commands that might be available + $pythonCommands = @("python", "py", "python3") + foreach ($cmd in $pythonCommands) { + try { + $version = & $cmd --version 2>&1 + if ($version -match "Python") { + Write-Host "Found Python via command '$cmd': $version" + return $cmd + } + } catch { + continue + } + } + + throw "Could not find any Python installation (mapped or otherwise)" +} + +try { + # Step 1: Find the mapped Python installation + Write-Host "Step 1: Finding mapped Python installation..." + $pythonExe = Find-MappedPython + Write-Host "Using Python: $pythonExe" + + # Verify Python works and show version + $pythonVersion = & $pythonExe --version 2>&1 + Write-Host "Python version: $pythonVersion" + + # Step 2: Install cua-computer-server directly + Write-Host "Step 2: Installing cua-computer-server..." + + Write-Host "Upgrading pip..." + & $pythonExe -m pip install --upgrade pip --quiet + + Write-Host "Installing cua-computer-server..." + & $pythonExe -m pip install cua-computer-server --quiet + + Write-Host "cua-computer-server installation completed." + + # Step 3: Start computer server in background + Write-Host "Step 3: Starting computer server in background..." + Write-Host "Starting computer server with: $pythonExe" + + # Start the computer server in the background + $serverProcess = Start-Process -FilePath $pythonExe -ArgumentList "-m", "computer_server.main" -WindowStyle Hidden -PassThru + Write-Host "Computer server started in background with PID: $($serverProcess.Id)" + + # Give it a moment to start + Start-Sleep -Seconds 3 + + # Check if the process is still running + if (Get-Process -Id $serverProcess.Id -ErrorAction SilentlyContinue) { + Write-Host "Computer server is running successfully in background" + } else { + throw "Computer server failed to start or exited immediately" + } + +} catch { + Write-Error "Setup failed: $_" + Write-Host "Error details: $($_.Exception.Message)" + Write-Host "Stack trace: $($_.ScriptStackTrace)" + Write-Host "" + Write-Host "Press any key to close this window..." + $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") + exit 1 +} + +Write-Host "" +Write-Host "Setup completed successfully!" +Write-Host "Press any key to close this window..." +$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") From 91ebb81d733b01bd6f917cbbef005dafdb6468f0 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 17 Jun 2025 12:30:52 -0400 Subject: [PATCH 045/141] added __main__.py for python -m computer.ui shorthand --- libs/computer/computer/ui/__main__.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 libs/computer/computer/ui/__main__.py diff --git a/libs/computer/computer/ui/__main__.py b/libs/computer/computer/ui/__main__.py new file mode 100644 index 00000000..abd16f82 --- /dev/null +++ b/libs/computer/computer/ui/__main__.py @@ -0,0 +1,15 @@ +""" +Main entry point for computer.ui module. + +This allows running the computer UI with: + python -m computer.ui + +Instead of: + python -m computer.ui.gradio.app +""" + +from .gradio.app import create_gradio_ui + +if __name__ == "__main__": + app = create_gradio_ui() + app.launch() From 7cfe63e50b7256e5452446f65c83c3ec08bccd7e Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 17 Jun 2025 12:37:11 -0400 Subject: [PATCH 046/141] Fix hotkeys on windows --- .../computer_server/handlers/windows.py | 2 +- .../computer/providers/winsandbox/__init__.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 libs/computer/computer/providers/winsandbox/__init__.py diff --git a/libs/computer-server/computer_server/handlers/windows.py b/libs/computer-server/computer_server/handlers/windows.py index 269b97b6..d88dfe0b 100644 --- a/libs/computer-server/computer_server/handlers/windows.py +++ b/libs/computer-server/computer_server/handlers/windows.py @@ -277,7 +277,7 @@ class WindowsAutomationHandler(BaseAutomationHandler): except Exception as e: return {"success": False, "error": str(e)} - async def hotkey(self, *keys: str) -> Dict[str, Any]: + async def hotkey(self, keys: str) -> Dict[str, Any]: if not pyautogui: return {"success": False, "error": "pyautogui not available"} diff --git a/libs/computer/computer/providers/winsandbox/__init__.py b/libs/computer/computer/providers/winsandbox/__init__.py new file mode 100644 index 00000000..715ed7db --- /dev/null +++ b/libs/computer/computer/providers/winsandbox/__init__.py @@ -0,0 +1,11 @@ +"""Windows Sandbox provider for CUA Computer.""" + +try: + import winsandbox + HAS_WINSANDBOX = True +except ImportError: + HAS_WINSANDBOX = False + +from .provider import WinSandboxProvider + +__all__ = ["WinSandboxProvider", "HAS_WINSANDBOX"] From f08c018b502bcde875664bad0d42baaf97e24fcf Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 17 Jun 2025 12:37:55 -0400 Subject: [PATCH 047/141] Added winsandbox to gradio UI --- libs/computer/computer/ui/gradio/app.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libs/computer/computer/ui/gradio/app.py b/libs/computer/computer/ui/gradio/app.py index 946fb203..8c3708db 100644 --- a/libs/computer/computer/ui/gradio/app.py +++ b/libs/computer/computer/ui/gradio/app.py @@ -574,6 +574,12 @@ async def handle_init_computer(os_choice: str, app_list=None, provider="lume", c api_key=cloud_api_key, experiments=experiments ) + elif provider == "winsandbox": + computer = Computer( + os_type="windows", + provider_type=VMProviderType.WINSANDBOX, + experiments=experiments + ) else: computer = Computer( image=image_str, @@ -1091,7 +1097,7 @@ def create_gradio_ui(): # Provider selection radio provider_choice = gr.Radio( label="Provider", - choices=["lume", "self", "cloud"], + choices=["lume", "self", "cloud", "winsandbox"], value="lume", info="'lume' uses a VM, 'self' uses the host computer server, 'cloud' uses a cloud container" ) From 2f472ffe163796e968915b79b657413b1670fd8e Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 17 Jun 2025 12:51:21 -0400 Subject: [PATCH 048/141] Removed logging and delay --- .../computer/providers/winsandbox/provider.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/libs/computer/computer/providers/winsandbox/provider.py b/libs/computer/computer/providers/winsandbox/provider.py index 53d0148e..a0b61055 100644 --- a/libs/computer/computer/providers/winsandbox/provider.py +++ b/libs/computer/computer/providers/winsandbox/provider.py @@ -416,7 +416,6 @@ class WinSandboxProvider(BaseVMProvider): """ try: self.logger.info(f"Setting up computer server in sandbox {name}...") - print(f"Setting up computer server in sandbox {name}...") # Read the PowerShell setup script script_path = os.path.join(os.path.dirname(__file__), "setup_script.ps1") @@ -426,12 +425,12 @@ class WinSandboxProvider(BaseVMProvider): # Write the setup script to the sandbox using RPyC script_dest_path = r"C:\Users\WDAGUtilityAccount\setup_cua.ps1" - print(f"Writing setup script to {script_dest_path}") + self.logger.info(f"Writing setup script to {script_dest_path}") with sandbox.rpyc.builtin.open(script_dest_path, 'w') as f: f.write(setup_script_content) # Execute the PowerShell script in the background - print("Executing setup script in sandbox...") + self.logger.info("Executing setup script in sandbox...") # Use subprocess to run PowerShell script import subprocess @@ -447,8 +446,7 @@ class WinSandboxProvider(BaseVMProvider): # CREATE_NEW_CONSOLE - creates a new console window (visible) creation_flags = 0x00000010 else: - # DETACHED_PROCESS - runs in background (not visible) - creation_flags = 0x00000008 + creation_flags = 0x08000000 # CREATE_NO_WINDOW # Start the process using RPyC process = sandbox.rpyc.modules.subprocess.Popen( @@ -457,12 +455,12 @@ class WinSandboxProvider(BaseVMProvider): shell=False ) - # Sleep for 30 seconds - await asyncio.sleep(30) + # # Sleep for 30 seconds + # await asyncio.sleep(30) ip = await self.get_ip(name) - print(f"Sandbox IP: {ip}") - print(f"Setup script started in background in sandbox {name} with PID: {process.pid}") + self.logger.info(f"Sandbox IP: {ip}") + self.logger.info(f"Setup script started in background in sandbox {name} with PID: {process.pid}") except Exception as e: self.logger.error(f"Failed to setup computer server in sandbox {name}: {e}") From cd64df770f92d986cd218c6a550c83beda8f11a7 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 17 Jun 2025 12:53:18 -0400 Subject: [PATCH 049/141] Added agent.ui shorthand --- libs/agent/agent/ui/__main__.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 libs/agent/agent/ui/__main__.py diff --git a/libs/agent/agent/ui/__main__.py b/libs/agent/agent/ui/__main__.py new file mode 100644 index 00000000..a40684a8 --- /dev/null +++ b/libs/agent/agent/ui/__main__.py @@ -0,0 +1,15 @@ +""" +Main entry point for agent.ui module. + +This allows running the agent UI with: + python -m agent.ui + +Instead of: + python -m agent.ui.gradio.app +""" + +from .gradio.app import create_gradio_ui + +if __name__ == "__main__": + app = create_gradio_ui() + app.launch(share=False, inbrowser=True) From d2a0ee02224a2d939c866ab439842c3f093538ab Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 17 Jun 2025 12:55:26 -0400 Subject: [PATCH 050/141] Added winsandbox to agent UI --- libs/agent/agent/ui/gradio/app.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/libs/agent/agent/ui/gradio/app.py b/libs/agent/agent/ui/gradio/app.py index 8d173dd6..89f9894b 100644 --- a/libs/agent/agent/ui/gradio/app.py +++ b/libs/agent/agent/ui/gradio/app.py @@ -735,14 +735,19 @@ if __name__ == "__main__": info="Select the operating system for the computer", ) - # Detect if current device is MacOS + is_windows = platform.system().lower() == "windows" is_mac = platform.system().lower() == "darwin" + providers = ["cloud"] + if is_mac: + providers += ["lume"] + elif is_windows: + providers += ["winsandbox"] + computer_provider = gr.Radio( - choices=["cloud", "lume"], + choices=providers, label="Provider", - value="lume" if is_mac else "cloud", - visible=is_mac, + value="cloud", info="Select the computer provider", ) From eb8ecad9dd7a75076129e31b52ee80b71d66fbe9 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 17 Jun 2025 12:56:25 -0400 Subject: [PATCH 051/141] Added windows OS option --- libs/agent/agent/ui/gradio/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/agent/agent/ui/gradio/app.py b/libs/agent/agent/ui/gradio/app.py index 89f9894b..fc328518 100644 --- a/libs/agent/agent/ui/gradio/app.py +++ b/libs/agent/agent/ui/gradio/app.py @@ -729,7 +729,7 @@ if __name__ == "__main__": with gr.Accordion("Computer Configuration", open=True): # Computer configuration options computer_os = gr.Radio( - choices=["macos", "linux"], + choices=["macos", "linux", "windows"], label="Operating System", value="macos", info="Select the operating system for the computer", @@ -747,7 +747,7 @@ if __name__ == "__main__": computer_provider = gr.Radio( choices=providers, label="Provider", - value="cloud", + value="lume" if is_mac else "cloud", info="Select the computer provider", ) From d097ad9c7b6483f7114615966f087e3a7dc2d0a0 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 17 Jun 2025 12:59:35 -0400 Subject: [PATCH 052/141] Added windows sandbox example --- examples/winsandbox_example.py | 48 ++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 examples/winsandbox_example.py diff --git a/examples/winsandbox_example.py b/examples/winsandbox_example.py new file mode 100644 index 00000000..2f7e3f68 --- /dev/null +++ b/examples/winsandbox_example.py @@ -0,0 +1,48 @@ +"""Example of using the Windows Sandbox computer provider. + +Learn more at: https://learn.microsoft.com/en-us/windows/security/application-security/application-isolation/windows-sandbox/ +""" + +import asyncio +from computer import Computer + +async def main(): + """Test the Windows Sandbox provider.""" + + # Create a computer instance using Windows Sandbox + computer = Computer( + provider_type="winsandbox", + os_type="windows", + memory="4GB", + # ephemeral=True, # Always true for Windows Sandbox + ) + + try: + print("Starting Windows Sandbox...") + await computer.run() + + print("Windows Sandbox is ready!") + print(f"IP Address: {await computer.get_ip()}") + + # Test basic functionality + print("Testing basic functionality...") + screenshot = await computer.interface.screenshot() + print(f"Screenshot taken: {len(screenshot)} bytes") + + # Test running a command + print("Testing command execution...") + stdout, stderr = await computer.interface.run_command("echo Hello from Windows Sandbox!") + print(f"Command output: {stdout}") + + except Exception as e: + print(f"Error: {e}") + import traceback + traceback.print_exc() + + finally: + print("Stopping Windows Sandbox...") + await computer.stop() + print("Windows Sandbox stopped.") + +if __name__ == "__main__": + asyncio.run(main()) From 0326b0108468e887dc9b3f838be73bd1bbab1fa4 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 17 Jun 2025 13:32:50 -0400 Subject: [PATCH 053/141] Removed unnecessary conditional --- libs/computer-server/computer_server/handlers/factory.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/libs/computer-server/computer_server/handlers/factory.py b/libs/computer-server/computer_server/handlers/factory.py index 31d02fee..962f7fb1 100644 --- a/libs/computer-server/computer_server/handlers/factory.py +++ b/libs/computer-server/computer_server/handlers/factory.py @@ -36,10 +36,9 @@ class HandlerFactory: return system # Fallback to uname if platform.system() doesn't return expected values (Unix-like systems only) - if system != 'windows': - result = subprocess.run(['uname', '-s'], capture_output=True, text=True) - if result.returncode == 0: - return result.stdout.strip().lower() + result = subprocess.run(['uname', '-s'], capture_output=True, text=True) + if result.returncode == 0: + return result.stdout.strip().lower() raise RuntimeError(f"Unsupported OS: {system}") except Exception as e: From f6bc54757914405f5e6cb18209160aa642b9bc5d Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Tue, 17 Jun 2025 13:38:32 -0400 Subject: [PATCH 054/141] Remove bad claude code, start from scratch with lume provider --- .../typescript/src/computer/computer.ts | 705 ------------------ .../typescript/src/computer/defaults.ts | 36 + .../computer/typescript/src/computer/index.ts | 51 +- .../typescript/src/computer/providers/base.ts | 106 +++ .../src/computer/providers/cloud.ts | 27 + .../typescript/src/computer/providers/lume.ts | 281 +++++++ .../src/computer/providers/lumier.ts | 60 ++ .../computer/typescript/src/computer/types.ts | 152 ++++ libs/computer/typescript/src/helpers.ts | 76 -- libs/computer/typescript/src/index.ts | 33 +- .../computer/typescript/src/interface/base.ts | 41 - .../typescript/src/interface/factory.ts | 56 -- .../typescript/src/interface/index.ts | 5 - .../typescript/src/interface/linux.ts | 47 -- .../typescript/src/interface/macos.ts | 47 -- .../typescript/src/interface/types.ts | 96 --- .../computer/typescript/src/providers/base.ts | 140 ---- .../typescript/src/providers/cloud/index.ts | 5 - .../src/providers/cloud/provider.ts | 68 -- .../typescript/src/providers/factory.ts | 150 ---- .../typescript/src/providers/index.ts | 12 - .../typescript/src/providers/lume/index.ts | 16 - .../typescript/src/providers/lume/provider.ts | 182 ----- .../typescript/src/providers/lume_api.ts | 265 ------- .../typescript/src/providers/lumier/index.ts | 16 - .../src/providers/lumier/provider.ts | 401 ---------- libs/computer/typescript/src/telemetry.ts | 117 --- libs/computer/typescript/src/types.ts | 21 - libs/computer/typescript/src/util/logger.ts | 7 + libs/computer/typescript/src/util/lume.ts | 565 ++++++++++++++ libs/computer/typescript/src/utils.ts | 118 --- libs/computer/typescript/tests/index.test.ts | 36 + .../typescript/tests/lume_api.test.ts | 625 ++++++++++++++++ 33 files changed, 1948 insertions(+), 2615 deletions(-) delete mode 100644 libs/computer/typescript/src/computer/computer.ts create mode 100644 libs/computer/typescript/src/computer/defaults.ts create mode 100644 libs/computer/typescript/src/computer/providers/base.ts create mode 100644 libs/computer/typescript/src/computer/providers/cloud.ts create mode 100644 libs/computer/typescript/src/computer/providers/lume.ts create mode 100644 libs/computer/typescript/src/computer/providers/lumier.ts create mode 100644 libs/computer/typescript/src/computer/types.ts delete mode 100644 libs/computer/typescript/src/helpers.ts delete mode 100644 libs/computer/typescript/src/interface/base.ts delete mode 100644 libs/computer/typescript/src/interface/factory.ts delete mode 100644 libs/computer/typescript/src/interface/index.ts delete mode 100644 libs/computer/typescript/src/interface/linux.ts delete mode 100644 libs/computer/typescript/src/interface/macos.ts delete mode 100644 libs/computer/typescript/src/interface/types.ts delete mode 100644 libs/computer/typescript/src/providers/base.ts delete mode 100644 libs/computer/typescript/src/providers/cloud/index.ts delete mode 100644 libs/computer/typescript/src/providers/cloud/provider.ts delete mode 100644 libs/computer/typescript/src/providers/factory.ts delete mode 100644 libs/computer/typescript/src/providers/index.ts delete mode 100644 libs/computer/typescript/src/providers/lume/index.ts delete mode 100644 libs/computer/typescript/src/providers/lume/provider.ts delete mode 100644 libs/computer/typescript/src/providers/lume_api.ts delete mode 100644 libs/computer/typescript/src/providers/lumier/index.ts delete mode 100644 libs/computer/typescript/src/providers/lumier/provider.ts delete mode 100644 libs/computer/typescript/src/telemetry.ts delete mode 100644 libs/computer/typescript/src/types.ts create mode 100644 libs/computer/typescript/src/util/logger.ts create mode 100644 libs/computer/typescript/src/util/lume.ts delete mode 100644 libs/computer/typescript/src/utils.ts create mode 100644 libs/computer/typescript/tests/index.test.ts create mode 100644 libs/computer/typescript/tests/lume_api.test.ts diff --git a/libs/computer/typescript/src/computer/computer.ts b/libs/computer/typescript/src/computer/computer.ts deleted file mode 100644 index eea9e563..00000000 --- a/libs/computer/typescript/src/computer/computer.ts +++ /dev/null @@ -1,705 +0,0 @@ -import type { Display, ComputerConfig } from "../types"; -import type { BaseComputerInterface } from "../interface/base"; -import { InterfaceFactory } from "../interface/factory"; -import type { BaseVMProvider } from "../providers/base"; -import { VMProviderType } from "../providers/base"; -import { VMProviderFactory } from "../providers/factory"; -import pino from "pino"; -import { - recordComputerInitialization, - recordVMStart, - recordVMStop, -} from "../telemetry"; -import { setDefaultComputer } from "../helpers"; -import { - parseDisplayString, - parseImageString, - sleep, - withTimeout, -} from "../utils"; -import sharp from "sharp"; - -export type OSType = "macos" | "linux" | "windows"; - -export interface ComputerOptions { - display?: Display | { width: number; height: number } | string; - memory?: string; - cpu?: string; - osType?: OSType; - name?: string; - image?: string; - sharedDirectories?: string[]; - useHostComputerServer?: boolean; - verbosity?: pino.Level; - telemetryEnabled?: boolean; - providerType?: VMProviderType | string; - port?: number; - noVNCPort?: number; - host?: string; - storage?: string; - ephemeral?: boolean; - apiKey?: string; - experiments?: string[]; -} - -/** - * Computer is the main class for interacting with the computer. - */ -export class Computer { - private logger: pino.Logger; - - private image: string; - private port?: number; - private noVNCPort?: number; - private host: string; - private osType: OSType; - private providerType: VMProviderType | string; - private ephemeral: boolean; - private apiKey?: string; - private experiments: string[]; - private storage?: string; - private sharedPath?: string; - private sharedDirectories: string[]; - private _telemetryEnabled: boolean; - private _initialized: boolean = false; - private _running: boolean = false; - - private useHostComputerServer: boolean; - private config?: ComputerConfig; - private _providerContext?: BaseVMProvider; - private _interface?: BaseComputerInterface; - private _stopEvent?: Promise; - private _keepAliveTask?: Promise; - - /** - * Initialize a new Computer instance. - * - * @param options Configuration options for the Computer - */ - constructor(options: ComputerOptions = {}) { - const { - display = "1024x768", - memory = "8GB", - cpu = "4", - osType = "macos", - name = "", - image = "macos-sequoia-cua:latest", - sharedDirectories = [], - useHostComputerServer = false, - verbosity = "info", - telemetryEnabled = true, - providerType = VMProviderType.LUME, - port = 7777, - noVNCPort = 8006, - host = process.env.PYLUME_HOST || "localhost", - storage, - ephemeral = false, - apiKey, - experiments = [], - } = options; - - this.logger = pino({ name: "cua.computer", level: verbosity }); - this.logger.info("Initializing Computer..."); - - // Store original parameters - this.image = image; - this.port = port; - this.noVNCPort = noVNCPort; - this.host = host; - this.osType = osType; - this.providerType = providerType; - this.ephemeral = ephemeral; - this.apiKey = apiKey; - this.experiments = experiments; - - if (this.experiments.includes("app-use")) { - if (this.osType !== "macos") { - throw new Error("App use experiment is only supported on macOS"); - } - } - - // The default is currently to use non-ephemeral storage - if (storage && ephemeral && storage !== "ephemeral") { - throw new Error( - "Storage path and ephemeral flag cannot be used together" - ); - } - this.storage = ephemeral ? "ephemeral" : storage; - - // For Lumier provider, store the first shared directory path to use - // for VM file sharing - this.sharedPath = undefined; - if (sharedDirectories && sharedDirectories.length > 0) { - this.sharedPath = sharedDirectories[0]; - this.logger.info( - `Using first shared directory for VM file sharing: ${this.sharedPath}` - ); - } - - // Store telemetry preference - this._telemetryEnabled = telemetryEnabled; - - this.useHostComputerServer = useHostComputerServer; - - if (!useHostComputerServer) { - const imageInfo = parseImageString(image); - - const vmName = name || image.replace(":", "_"); - - // Convert display parameter to Display object - let displayConfig: Display; - if (typeof display === "string") { - const { width, height } = parseDisplayString(display); - displayConfig = { width, height }; - } else if ("width" in display && "height" in display) { - displayConfig = display as Display; - } else { - displayConfig = display as Display; - } - - this.config = { - image: imageInfo.name, - tag: imageInfo.tag, - name: vmName, - display: displayConfig, - memory, - cpu, - }; - } - - // Store shared directories config - this.sharedDirectories = sharedDirectories; - - // Record initialization in telemetry (if enabled) - if (telemetryEnabled) { - recordComputerInitialization(); - } else { - this.logger.debug( - "Telemetry disabled - skipping initialization tracking" - ); - } - } - - /** - * Create a virtual desktop from a list of app names, returning a DioramaComputer - * that proxies Diorama.Interface but uses diorama_cmds via the computer interface. - * - * @param apps List of application names to include in the desktop. - * @returns A proxy object with the Diorama interface, but using diorama_cmds. - */ - createDesktopFromApps(apps: string[]): any { - if (!this.experiments.includes("app-use")) { - throw new Error( - "App Usage is an experimental feature. Enable it by passing experiments=['app-use'] to Computer()" - ); - } - // DioramaComputer would be imported and used here - throw new Error("DioramaComputer not yet implemented"); - } - - /** - * Start the computer (async context manager enter). - */ - async __aenter__(): Promise { - await this.run(); - return this; - } - - /** - * Stop the computer (async context manager exit). - */ - async __aexit__(excType: any, excVal: any, excTb: any): Promise { - await this.disconnect(); - } - - /** - * Initialize the VM and computer interface. - */ - async run(): Promise { - // If already initialized, just log and return - if (this._initialized) { - this.logger.info("Computer already initialized, skipping initialization"); - return; - } - - this.logger.info("Starting computer..."); - const startTime = Date.now(); - - try { - let ipAddress: string; - - // If using host computer server - if (this.useHostComputerServer) { - this.logger.info("Using host computer server"); - ipAddress = "localhost"; - - // Create the interface - this._interface = InterfaceFactory.createInterfaceForOS( - this.osType, - ipAddress - ); - - this.logger.info("Waiting for host computer server to be ready..."); - await this._interface.waitForReady(); - this.logger.info("Host computer server ready"); - } else { - // Start or connect to VM - this.logger.info(`Starting VM: ${this.image}`); - - if (!this._providerContext) { - try { - const providerTypeName = - typeof this.providerType === "object" - ? this.providerType - : this.providerType; - - this.logger.info( - `Initializing ${providerTypeName} provider context...` - ); - - // Create VM provider instance with explicit parameters - const providerOptions = { - port: this.port, - host: this.host, - storage: this.storage, - sharedPath: this.sharedPath, - image: this.image, - verbose: - this.logger.level === "debug" || this.logger.level === "trace", - ephemeral: this.ephemeral, - noVNCPort: this.noVNCPort, - apiKey: this.apiKey, - }; - - if (!this.config) { - throw new Error("Computer config not initialized"); - } - - this.config.vm_provider = await VMProviderFactory.createProvider( - this.providerType, - providerOptions - ); - - this._providerContext = await this.config.vm_provider.__aenter__(); - this.logger.debug("VM provider context initialized successfully"); - } catch (error) { - this.logger.error( - `Failed to import provider dependencies: ${error}` - ); - throw error; - } - } - - // Run the VM - if (!this.config || !this.config.vm_provider) { - throw new Error("VM provider not initialized"); - } - - const runOpts = { - display: this.config.display, - memory: this.config.memory, - cpu: this.config.cpu, - shared_directories: this.sharedDirectories, - }; - - this.logger.info( - `Running VM ${this.config.name} with options:`, - runOpts - ); - - if (this._telemetryEnabled) { - recordVMStart(this.config.name, String(this.providerType)); - } - - const storageParam = this.ephemeral ? "ephemeral" : this.storage; - - try { - await this.config.vm_provider.runVM( - this.image, - this.config.name, - runOpts, - storageParam - ); - } catch (error: any) { - if (error.message?.includes("already running")) { - this.logger.info(`VM ${this.config.name} is already running`); - } else { - throw error; - } - } - - // Wait for VM to be ready - try { - this.logger.info("Waiting for VM to be ready..."); - await this.waitVMReady(); - - // Get IP address - ipAddress = await this.getIP(); - this.logger.info(`VM is ready with IP: ${ipAddress}`); - } catch (error) { - this.logger.error(`Error waiting for VM: ${error}`); - throw new Error(`VM failed to become ready: ${error}`); - } - } - - // Initialize the interface - try { - // Verify we have a valid IP before initializing the interface - if (!ipAddress || ipAddress === "unknown" || ipAddress === "0.0.0.0") { - throw new Error( - `Cannot initialize interface - invalid IP address: ${ipAddress}` - ); - } - - this.logger.info( - `Initializing interface for ${this.osType} at ${ipAddress}` - ); - - // Pass authentication credentials if using cloud provider - if ( - this.providerType === VMProviderType.CLOUD && - this.apiKey && - this.config?.name - ) { - this._interface = InterfaceFactory.createInterfaceForOS( - this.osType, - ipAddress, - this.apiKey, - this.config.name - ); - } else { - this._interface = InterfaceFactory.createInterfaceForOS( - this.osType, - ipAddress - ); - } - - // Wait for the WebSocket interface to be ready - this.logger.info("Connecting to WebSocket interface..."); - - try { - await withTimeout( - this._interface.waitForReady(), - 30000, - `Could not connect to WebSocket interface at ${ipAddress}:8000/ws` - ); - this.logger.info("WebSocket interface connected successfully"); - } catch (error) { - this.logger.error( - `Failed to connect to WebSocket interface at ${ipAddress}` - ); - throw error; - } - - // Create an event to keep the VM running in background if needed - if (!this.useHostComputerServer) { - // In TypeScript, we'll use a Promise instead of asyncio.Event - let resolveStop: () => void; - this._stopEvent = new Promise((resolve) => { - resolveStop = resolve; - }); - this._keepAliveTask = this._stopEvent; - } - - this.logger.info("Computer is ready"); - - // Set the initialization flag - this._initialized = true; - - // Set this instance as the default computer for remote decorators - setDefaultComputer(this); - - this.logger.info("Computer successfully initialized"); - } catch (error) { - throw error; - } finally { - // Log initialization time for performance monitoring - const durationMs = Date.now() - startTime; - this.logger.debug( - `Computer initialization took ${durationMs.toFixed(2)}ms` - ); - } - } catch (error) { - this.logger.error(`Failed to initialize computer: ${error}`); - throw new Error(`Failed to initialize computer: ${error}`); - } - - return; - } - - /** - * Disconnect from the computer's WebSocket interface. - */ - async disconnect(): Promise { - if (this._interface) { - // Note: The interface close method would need to be implemented - // this._interface.close(); - } - } - - /** - * Disconnect from the computer's WebSocket interface and stop the computer. - */ - async stop(): Promise { - const startTime = Date.now(); - - try { - this.logger.info("Stopping Computer..."); - - // In VM mode, first explicitly stop the VM, then exit the provider context - if ( - !this.useHostComputerServer && - this._providerContext && - this.config?.vm_provider - ) { - try { - this.logger.info(`Stopping VM ${this.config.name}...`); - await this.config.vm_provider.stopVM(this.config.name, this.storage); - } catch (error) { - this.logger.error(`Error stopping VM: ${error}`); - } - - this.logger.info("Closing VM provider context..."); - await this.config.vm_provider.__aexit__(null, null, null); - this._providerContext = undefined; - } - - await this.disconnect(); - this.logger.info("Computer stopped"); - } catch (error) { - this.logger.debug(`Error during cleanup: ${error}`); - } finally { - // Log stop time for performance monitoring - const durationMs = Date.now() - startTime; - this.logger.debug( - `Computer stop process took ${durationMs.toFixed(2)}ms` - ); - - if (this._telemetryEnabled && this.config?.name) { - recordVMStop(this.config.name, durationMs); - } - } - } - - /** - * Get the IP address of the VM or localhost if using host computer server. - */ - async getIP( - maxRetries: number = 15, - retryDelay: number = 2 - ): Promise { - // For host computer server, always return localhost immediately - if (this.useHostComputerServer) { - return "127.0.0.1"; - } - - // Get IP from the provider - if (!this.config?.vm_provider) { - throw new Error("VM provider is not initialized"); - } - - // Log that we're waiting for the IP - this.logger.info( - `Waiting for VM ${this.config.name} to get an IP address...` - ); - - // Call the provider's get_ip method which will wait indefinitely - const storageParam = this.ephemeral ? "ephemeral" : this.storage; - - // Log the image being used - this.logger.info(`Running VM using image: ${this.image}`); - - // Call provider.getIP with explicit parameters - const ip = await this.config.vm_provider.getIP( - this.config.name, - storageParam, - retryDelay - ); - - // Log success - this.logger.info(`VM ${this.config.name} has IP address: ${ip}`); - return ip; - } - - /** - * Wait for VM to be ready with an IP address. - */ - async waitVMReady(): Promise | undefined> { - if (this.useHostComputerServer) { - return undefined; - } - - const timeout = 600; // 10 minutes timeout - const interval = 2.0; // 2 seconds between checks - const startTime = Date.now() / 1000; - let lastStatus: string | undefined; - let attempts = 0; - - this.logger.info( - `Waiting for VM ${this.config?.name} to be ready (timeout: ${timeout}s)...` - ); - - while (Date.now() / 1000 - startTime < timeout) { - attempts++; - const elapsed = Date.now() / 1000 - startTime; - - try { - // Keep polling for VM info - if (!this.config?.vm_provider) { - this.logger.error("VM provider is not initialized"); - return undefined; - } - - const vm = await this.config.vm_provider.getVM(this.config.name); - - // Log full VM properties for debugging (every 30 attempts) - if (attempts % 30 === 0) { - this.logger.info( - `VM properties at attempt ${attempts}: ${JSON.stringify(vm)}` - ); - } - - // Get current status for logging - const currentStatus = vm?.status; - if (currentStatus !== lastStatus) { - this.logger.info( - `VM status changed to: ${currentStatus} (after ${elapsed.toFixed( - 1 - )}s)` - ); - lastStatus = currentStatus; - } - - // Check if VM is ready - if ( - vm && - vm.status === "running" && - vm.ip_address && - vm.ip_address !== "0.0.0.0" - ) { - this.logger.info( - `VM ${this.config.name} is ready with IP: ${vm.ip_address}` - ); - return vm; - } - - // Wait before next check - await sleep(interval * 1000); - } catch (error) { - this.logger.error(`Error checking VM status: ${error}`); - await sleep(interval * 1000); - } - } - - throw new Error( - `VM ${this.config?.name} failed to become ready within ${timeout} seconds` - ); - } - - /** - * Update VM settings. - */ - async update(cpu?: number, memory?: string): Promise { - if (this.useHostComputerServer) { - this.logger.warn("Cannot update settings for host computer server"); - return; - } - - if (!this.config?.vm_provider) { - throw new Error("VM provider is not initialized"); - } - - await this.config.vm_provider.updateVM( - this.config.name, - cpu, - memory, - this.storage - ); - } - - /** - * Get the dimensions of a screenshot. - */ - async getScreenshotSize( - screenshot: Buffer - ): Promise<{ width: number; height: number }> { - const metadata = await sharp(screenshot).metadata(); - return { - width: metadata.width || 0, - height: metadata.height || 0, - }; - } - - /** - * Get the computer interface for interacting with the VM. - */ - get interface(): BaseComputerInterface { - if (!this._interface) { - throw new Error("Computer interface not initialized. Call run() first."); - } - return this._interface; - } - - /** - * Check if telemetry is enabled for this computer instance. - */ - get telemetryEnabled(): boolean { - return this._telemetryEnabled; - } - - /** - * Convert normalized coordinates to screen coordinates. - */ - toScreenCoordinates(x: number, y: number): [number, number] { - if (!this.config?.display) { - throw new Error("Display configuration not available"); - } - return [x * this.config.display.width, y * this.config.display.height]; - } - - /** - * Convert screen coordinates to screenshot coordinates. - */ - async toScreenshotCoordinates( - x: number, - y: number - ): Promise<[number, number]> { - // In the Python version, this uses the interface to get screenshot dimensions - // For now, we'll assume 1:1 mapping - return [x, y]; - } - - /** - * Install packages in a virtual environment. - */ - async venvInstall( - venvName: string, - requirements: string[] - ): Promise<[string, string]> { - // This would be implemented using the interface to run commands - // TODO: Implement venvInstall - throw new Error("venvInstall not yet implemented"); - } - - /** - * Execute a shell command in a virtual environment. - */ - async venvCmd(venvName: string, command: string): Promise<[string, string]> { - // This would be implemented using the interface to run commands - // TODO: Implement venvCmd - throw new Error("venvCmd not yet implemented"); - } - - /** - * Execute function in a virtual environment using source code extraction. - */ - async venvExec( - venvName: string, - pythonFunc: Function, - ...args: any[] - ): Promise { - // This would be implemented using the interface to run Python code - // TODO: Implement venvExec - throw new Error("venvExec not yet implemented"); - } -} diff --git a/libs/computer/typescript/src/computer/defaults.ts b/libs/computer/typescript/src/computer/defaults.ts new file mode 100644 index 00000000..26e6d3c4 --- /dev/null +++ b/libs/computer/typescript/src/computer/defaults.ts @@ -0,0 +1,36 @@ +import { OSType, VMProviderType } from "./types"; +import type { BaseComputerConfig, Display } from "./types"; + +/** + * Default configuration values for Computer + */ +export const DEFAULT_CONFIG: Partial = { + name: "", + osType: OSType.MACOS, + vmProvider: VMProviderType.LUME, + display: "1024x768", + memory: "8GB", + cpu: 4, + image: "macos-sequoia-cua:latest", + sharedDirectories: [], + useHostComputerServer: false, + telemetryEnabled: true, + port: 7777, + noVNCPort: 8006, + host: "localhost", + ephemeral: false, +}; + +/** + * Apply default values to a computer configuration + * @param config Partial configuration + * @returns Complete configuration with defaults applied + */ +export function applyDefaults( + config: Partial +): T { + return { + ...DEFAULT_CONFIG, + ...config, + } as T; +} diff --git a/libs/computer/typescript/src/computer/index.ts b/libs/computer/typescript/src/computer/index.ts index 706e2bda..e8fde0e5 100644 --- a/libs/computer/typescript/src/computer/index.ts +++ b/libs/computer/typescript/src/computer/index.ts @@ -1,2 +1,49 @@ -// Re-export the Computer class and related types -export * from './computer'; +import { LumierComputer } from "./providers/lumier"; +import type { BaseComputer } from "./providers/base"; +import { CloudComputer } from "./providers/cloud"; +import { LumeComputer } from "./providers/lume"; +import { + VMProviderType, + type BaseComputerConfig, + type CloudComputerConfig, + type LumeComputerConfig, + type LumierComputerConfig, +} from "./types"; +import { applyDefaults } from "./defaults"; + +/** + * Factory class for creating the appropriate Computer instance + */ +export class Computer { + /** + * Create a computer instance based on the provided configuration + * @param config The computer configuration + * @returns The appropriate computer instance based on the VM provider type + */ + static create( + config: + | Partial + | Partial + | Partial + | Partial + ): BaseComputer { + // Apply defaults to the configuration + const fullConfig = applyDefaults(config); + + // Check the vmProvider property to determine which type of computer to create + switch (fullConfig.vmProvider) { + case VMProviderType.CLOUD: + return new CloudComputer(fullConfig as CloudComputerConfig); + case VMProviderType.LUME: + return new LumeComputer(fullConfig as LumeComputerConfig); + case VMProviderType.LUMIER: + return new LumierComputer(fullConfig as LumierComputerConfig); + default: + throw new Error( + `Unsupported VM provider type: ${fullConfig.vmProvider}` + ); + } + + throw new Error(`Unsupported VM provider type`); + } +} diff --git a/libs/computer/typescript/src/computer/providers/base.ts b/libs/computer/typescript/src/computer/providers/base.ts new file mode 100644 index 00000000..36211ba3 --- /dev/null +++ b/libs/computer/typescript/src/computer/providers/base.ts @@ -0,0 +1,106 @@ +import type { + BaseComputerConfig, + Display, + OSType, + VMProviderType, +} from "../types"; +import { computerLogger } from "../../util/logger"; + +/** + * Base Computer class with shared functionality + */ +export abstract class BaseComputer { + protected name: string; + protected osType: OSType; + protected vmProvider: VMProviderType; + + constructor(config: BaseComputerConfig) { + this.name = config.name; + this.osType = config.osType; + this.vmProvider = config.vmProvider; + } + + /** + * Get the name of the computer + */ + getName(): string { + return this.name; + } + + /** + * Get the OS type of the computer + */ + getOSType(): OSType { + return this.osType; + } + + /** + * Get the VM provider type + */ + getVMProviderType(): VMProviderType { + return this.vmProvider; + } + + /** + * Shared method available to all computer types + */ + async disconnect(): Promise { + computerLogger.info(`Disconnecting from ${this.name}`); + // Implementation would go here + } + + /** + * Parse display string into Display object + * @param display Display string in format "WIDTHxHEIGHT" + * @returns Display object + */ + public static parseDisplayString(display: string): Display { + const match = display.match(/^(\d+)x(\d+)$/); + if (!match) { + throw new Error( + `Invalid display format: ${display}. Expected format: WIDTHxHEIGHT` + ); + } + + return { + width: parseInt(match[1], 10), + height: parseInt(match[2], 10), + }; + } + + /** + * Parse memory string to MB integer. + * + * Examples: + * "8GB" -> 8192 + * "1024MB" -> 1024 + * "512" -> 512 + * + * @param memoryStr - Memory string to parse + * @returns Memory value in MB + */ + public static parseMemoryString(memoryStr: string): number { + if (!memoryStr) { + return 0; + } + + // Convert to uppercase for case-insensitive matching + const upperStr = memoryStr.toUpperCase().trim(); + + // Extract numeric value and unit + const match = upperStr.match(/^(\d+(?:\.\d+)?)\s*(GB|MB)?$/); + if (!match) { + throw new Error(`Invalid memory format: ${memoryStr}`); + } + + const value = parseFloat(match[1]); + const unit = match[2] || "MB"; // Default to MB if no unit specified + + // Convert to MB + if (unit === "GB") { + return Math.round(value * 1024); + } else { + return Math.round(value); + } + } +} diff --git a/libs/computer/typescript/src/computer/providers/cloud.ts b/libs/computer/typescript/src/computer/providers/cloud.ts new file mode 100644 index 00000000..be318aa8 --- /dev/null +++ b/libs/computer/typescript/src/computer/providers/cloud.ts @@ -0,0 +1,27 @@ +import { BaseComputer } from "./base"; +import type { CloudComputerConfig } from "../types"; +import { computerLogger } from "../../util/logger"; + +/** + * Cloud-specific computer implementation + */ +export class CloudComputer extends BaseComputer { + constructor(config: CloudComputerConfig) { + super(config); + } + + /** + * Cloud-specific method to deploy the computer + */ + async deploy(): Promise { + computerLogger.info(`Deploying cloud computer ${this.name}`); + // Cloud-specific implementation + } + + /** + * Cloud-specific method to get deployment status + */ + async getDeploymentStatus(): Promise { + return "running"; // Example implementation + } +} diff --git a/libs/computer/typescript/src/computer/providers/lume.ts b/libs/computer/typescript/src/computer/providers/lume.ts new file mode 100644 index 00000000..9b2d3674 --- /dev/null +++ b/libs/computer/typescript/src/computer/providers/lume.ts @@ -0,0 +1,281 @@ +import type { Display, LumeComputerConfig } from "../types"; +import { BaseComputer } from "./base"; +import { applyDefaults } from "../defaults"; +import { + lumeApiGet, + lumeApiRun, + lumeApiPull, + lumeApiStop, + lumeApiDelete, + lumeApiUpdate, + type VMInfo, +} from "../../util/lume"; +import pino from "pino"; + +const logger = pino({ name: "lume_computer" }); + +/** + * Lume-specific computer implementation + */ +export class LumeComputer extends BaseComputer { + private display: string | Display; + private memory: string; + private cpu: number; + private image: string; + private port: number; + private host: string; + private ephemeral: boolean; + + constructor(config: LumeComputerConfig) { + super(config); + + const defaultConfig = applyDefaults(config); + + this.display = defaultConfig.display; + this.memory = defaultConfig.memory; + this.cpu = defaultConfig.cpu; + this.image = defaultConfig.image; + this.port = defaultConfig.port; + this.host = defaultConfig.host; + this.ephemeral = defaultConfig.ephemeral; + } + + /** + * Lume-specific method to get a VM + */ + + async getVm(name: string, storage?: string): Promise { + try { + const vmInfo = (await lumeApiGet(name, this.host, this.port, storage))[0]; + if (!vmInfo) throw new Error("VM Not Found."); + if (vmInfo.status === "stopped") { + logger.info( + `VM ${name} is in '${vmInfo.status}' state - not waiting for IP address` + ); + return { + ...vmInfo, + name, + status: vmInfo.status, + }; + } + if (!vmInfo.ipAddress) { + logger.info( + `VM ${name} is in '${vmInfo.status}' state but no IP address found - reporting as still starting` + ); + } + return vmInfo; + } catch (e) { + logger.error(`Failed to get VM status: ${e}`); + throw e; + } + } + + /** + * Lume-specific method to list availalbe VMs + */ + + async listVm() { + const vms = await lumeApiGet("", this.host, this.port); + return vms; + } + + /** + * Lume-specific method to run the VM + */ + async runVm( + image: string, + name: string, + runOpts: { [key: string]: any } = {}, + storage?: string + ): Promise { + logger.info( + `Running Lume computer ${this.name} with ${this.memory} memory and ${this.cpu} CPUs` + ); + logger.info( + `Using image ${this.image} with display ${ + typeof this.display === "string" + ? this.display + : `${this.display.width}x${this.display.height}` + }` + ); + // Lume-specific implementation + try { + await this.getVm(name, storage); + } catch (e) { + logger.info( + `VM ${name} not found, attempting to pull image ${image} from registry...` + ); + // Call pull_vm with the image parameter + try { + const pullRes = await this.pullVm(name, image, storage); + logger.info(pullRes); + } catch (e) { + logger.info(`Failed to pull VM image: ${e}`); + throw e; + } + } + logger.info(`Running VM ${name} with options: ${runOpts}`); + return await lumeApiRun(name, this.host, this.port, runOpts, storage); + } + + /** + * Lume-specific method to stop a VM + */ + async stopVm(name: string, storage?: string): Promise { + // Stop the VM first + const stopResult = await lumeApiStop(name, this.host, this.port, storage); + + // If ephemeral mode is enabled, delete the VM after stopping + if (this.ephemeral && (!stopResult || !("error" in stopResult))) { + logger.info( + `Ephemeral mode enabled - deleting VM ${name} after stopping` + ); + try { + const deleteResult = await this.deleteVm(name, storage); + + // Return combined result + return { + ...stopResult, + deleted: true, + deleteResult: deleteResult, + } as VMInfo; + } catch (e) { + logger.error(`Failed to delete ephemeral VM ${name}: ${e}`); + throw new Error(`Failed to delete ephemeral VM ${name}: ${e}`); + } + } + + // Just return the stop result if not ephemeral + return stopResult; + } + + /** + * Lume-specific method to pull a VM image from the registry + */ + async pullVm( + name: string, + image: string, + storage?: string, + registry: string = "ghcr.io", + organization: string = "trycua", + pullOpts?: { [key: string]: any } + ): Promise { + // Validate image parameter + if (!image) { + throw new Error("Image parameter is required for pullVm"); + } + + logger.info(`Pulling VM image '${image}' as '${name}'`); + logger.info("You can check the pull progress using: lume logs -f"); + logger.debug(`Pull storage location: ${storage || "default"}`); + + try { + const result = await lumeApiPull( + image, + name, + this.host, + this.port, + storage, + registry, + organization + ); + + logger.info(`Successfully pulled VM image '${image}' as '${name}'`); + return result; + } catch (e) { + logger.error(`Failed to pull VM image '${image}': ${e}`); + throw new Error(`Failed to pull VM: ${e}`); + } + } + + /** + * Lume-specific method to delete a VM permanently + */ + async deleteVm(name: string, storage?: string): Promise { + logger.info(`Deleting VM ${name}...`); + + try { + const result = await lumeApiDelete( + name, + this.host, + this.port, + storage, + false, + false + ); + + logger.info(`Successfully deleted VM '${name}'`); + return result; + } catch (e) { + logger.error(`Failed to delete VM '${name}': ${e}`); + throw new Error(`Failed to delete VM: ${e}`); + } + } + + /** + * Lume-specific method to update VM configuration + */ + async updateVm( + name: string, + updateOpts: { [key: string]: any }, + storage?: string + ): Promise { + return await lumeApiUpdate( + name, + this.host, + this.port, + updateOpts, + storage, + false, + false + ); + } + + /** + * Lume-specific method to get the IP address of a VM, waiting indefinitely until it's available + */ + async getIp( + name: string, + storage?: string, + retryDelay: number = 2 + ): Promise { + // Track total attempts for logging purposes + let attempts = 0; + + while (true) { + attempts++; + + try { + const vmInfo = await this.getVm(name, storage); + + // Check if VM has an IP address + if (vmInfo.ipAddress) { + logger.info( + `Got IP address for VM ${name} after ${attempts} attempts: ${vmInfo.ipAddress}` + ); + return vmInfo.ipAddress; + } + + // Check if VM is in a state where it won't get an IP + if (vmInfo.status === "stopped" || vmInfo.status === "error") { + throw new Error( + `VM ${name} is in '${vmInfo.status}' state and will not get an IP address` + ); + } + + // Log progress every 10 attempts + if (attempts % 10 === 0) { + logger.info( + `Still waiting for IP address for VM ${name} (${attempts} attempts)...` + ); + } + + // Wait before retrying + await new Promise((resolve) => setTimeout(resolve, retryDelay * 1000)); + } catch (e) { + logger.error(`Error getting IP for VM ${name}: ${e}`); + throw e; + } + } + } +} diff --git a/libs/computer/typescript/src/computer/providers/lumier.ts b/libs/computer/typescript/src/computer/providers/lumier.ts new file mode 100644 index 00000000..ae18be76 --- /dev/null +++ b/libs/computer/typescript/src/computer/providers/lumier.ts @@ -0,0 +1,60 @@ +import { BaseComputer } from "./base"; +import { applyDefaults } from "../defaults"; +import type { Display, LumierComputerConfig } from "../types"; +import { computerLogger } from "../../util/logger"; + +/** + * Lumier-specific computer implementation + */ +export class LumierComputer extends BaseComputer { + private display: string | Display; + private memory: string; + private cpu: number; + private image: string; + private sharedDirectories?: string[]; + private noVNCPort?: number; + private storage?: string; + private ephemeral: boolean; + + constructor(config: LumierComputerConfig) { + super(config); + + const defaultConfig = applyDefaults(config); + + this.display = defaultConfig.display; + this.memory = defaultConfig.memory; + this.cpu = defaultConfig.cpu; + this.image = defaultConfig.image; + this.sharedDirectories = defaultConfig.sharedDirectories; + this.noVNCPort = defaultConfig.noVNCPort; + this.storage = defaultConfig.storage; + this.ephemeral = defaultConfig.ephemeral; + } + + /** + * Lumier-specific method to start the container + */ + async startContainer(): Promise { + computerLogger.info( + `Starting Lumier container ${this.name} with ${this.memory} memory and ${this.cpu} CPUs` + ); + computerLogger.info( + `Using image ${this.image} with display ${ + typeof this.display === "string" + ? this.display + : `${this.display.width}x${this.display.height}` + }` + ); + // Lumier-specific implementation + } + + /** + * Lumier-specific method to execute a command in the container + */ + async execCommand(command: string): Promise { + computerLogger.info( + `Executing command in Lumier container ${this.name}: ${command}` + ); + return "command output"; // Example implementation + } +} diff --git a/libs/computer/typescript/src/computer/types.ts b/libs/computer/typescript/src/computer/types.ts new file mode 100644 index 00000000..ed2bd02c --- /dev/null +++ b/libs/computer/typescript/src/computer/types.ts @@ -0,0 +1,152 @@ +/** + * Display configuration for the computer. + */ +export interface Display { + width: number; + height: number; + scale_factor?: number; +} + +/** + * Computer configuration model. + */ +export interface BaseComputerConfig { + /** + * The VM name + * @default "" + */ + name: string; + + /** + * The operating system type ('macos', 'windows', or 'linux') + * @default "macos" + */ + osType: OSType; + + /** + * The VM provider type to use (lume, lumier, cloud) + * @default VMProviderType.LUME + */ + vmProvider: VMProviderType; + + /** + * The display configuration. Can be: + * - A Display object + * - A dict with 'width' and 'height' + * - A string in format "WIDTHxHEIGHT" (e.g. "1920x1080") + * @default "1024x768" + */ + display?: Display | string; + + /** + * The VM memory allocation. (e.g. "8GB", "4GB", "1024MB") + * @default "8GB" + */ + memory?: string; + + /** + * The VM CPU allocation. + * @default 4 + */ + cpu?: number; + + /** + * The VM image name + * @default "macos-sequoia-cua:latest" + */ + image?: string; + + /** + * Optional list of directory paths to share with the VM + */ + sharedDirectories?: string[]; + + /** + * If True, target localhost instead of starting a VM + * @default false + */ + useHostComputerServer?: boolean; + + /** + * Whether to enable telemetry tracking. + * @default true + */ + telemetryEnabled?: boolean; + + /** + * Optional port to use for the VM provider server + * @default 7777 + */ + port?: number; + + /** + * Optional port for the noVNC web interface (Lumier provider) + * @default 8006 + */ + noVNCPort?: number; + + /** + * Host to use for VM provider connections (e.g. "localhost", "host.docker.internal") + * @default "localhost" + */ + host?: string; + + /** + * Optional path for persistent VM storage (Lumier provider) + */ + storage?: string; + + /** + * Whether to use ephemeral storage + * @default false + */ + ephemeral?: boolean; + + /** + * Optional list of experimental features to enable (e.g. ["app-use"]) + */ + experiments?: string[]; +} + +export interface CloudComputerConfig extends BaseComputerConfig { + /** + * Optional API key for cloud providers + */ + apiKey: string; + + /** + * Size of the cloud VM + */ + size: "small" | "medium" | "large"; + + /** + * The Cloud VM provider type + */ + vmProvider: VMProviderType.CLOUD; +} + +export interface LumeComputerConfig extends BaseComputerConfig { + /** + * The Lume VM provider type + */ + vmProvider: VMProviderType.LUME; +} + +/** + * The Lumier VM provider type + */ +export interface LumierComputerConfig extends BaseComputerConfig { + vmProvider: VMProviderType.LUMIER; +} + +export enum VMProviderType { + CLOUD = "cloud", + LUME = "lume", + LUMIER = "lumier", +} + +export enum OSType { + MACOS = "macos", + WINDOWS = "windows", + LINUX = "linux", +} diff --git a/libs/computer/typescript/src/helpers.ts b/libs/computer/typescript/src/helpers.ts deleted file mode 100644 index 44da61f8..00000000 --- a/libs/computer/typescript/src/helpers.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Helper functions and decorators for the Computer module. - */ - -import type { Computer } from './computer'; - -// Global reference to the default computer instance -let _defaultComputer: Computer | null = null; - -/** - * Set the default computer instance to be used by the remote decorator. - * - * @param computer The computer instance to use as default - */ -export function setDefaultComputer(computer: Computer): void { - _defaultComputer = computer; -} - -/** - * Get the default computer instance. - * - * @returns The default computer instance or null - */ -export function getDefaultComputer(): Computer | null { - return _defaultComputer; -} - -/** - * Decorator that wraps a function to be executed remotely via computer.venvExec - * - * @param venvName Name of the virtual environment to execute in - * @param computer The computer instance to use, or "default" to use the globally set default - * @param maxRetries Maximum number of retries for the remote execution - */ -export function sandboxed( - venvName: string = 'default', - computer: Computer | 'default' = 'default', - maxRetries: number = 3 -) { - return function any>( - target: any, - propertyKey: string, - descriptor: PropertyDescriptor - ) { - const originalMethod = descriptor.value; - - descriptor.value = async function (...args: Parameters): Promise> { - // Determine which computer instance to use - const comp = computer === 'default' ? _defaultComputer : computer; - - if (!comp) { - throw new Error( - 'No computer instance available. Either specify a computer instance or call setDefaultComputer() first.' - ); - } - - for (let i = 0; i < maxRetries; i++) { - try { - return await comp.venvExec(venvName, originalMethod, ...args); - } catch (error) { - console.error(`Attempt ${i + 1} failed:`, error); - if (i < maxRetries - 1) { - await new Promise(resolve => setTimeout(resolve, 1000)); - } else { - throw error; - } - } - } - - // This should never be reached, but satisfies TypeScript's control flow analysis - throw new Error('Unexpected: maxRetries loop completed without returning or throwing'); - }; - - return descriptor; - }; -} diff --git a/libs/computer/typescript/src/index.ts b/libs/computer/typescript/src/index.ts index 79eec2b5..7c3c7bb1 100644 --- a/libs/computer/typescript/src/index.ts +++ b/libs/computer/typescript/src/index.ts @@ -1,30 +1,5 @@ -// Core components -export { Computer } from "./computer"; -export type { ComputerOptions, OSType } from "./computer"; +// Export types +export * from "./computer/types"; -// Models -export type { Display, ComputerConfig } from "./types"; - -// Provider components -export { VMProviderType, BaseVMProviderImpl } from "./providers"; -export type { BaseVMProvider } from "./providers"; -export { VMProviderFactory } from "./providers"; -export type { VMProviderOptions } from "./providers"; - -// Interface components -export type { BaseComputerInterface } from "./interface"; -export { InterfaceFactory } from "./interface"; -export type { InterfaceOptions } from "./interface"; -export { Key } from "./interface"; -export type { - KeyType, - MouseButton, - NavigationKey, - SpecialKey, - ModifierKey, - FunctionKey, - AccessibilityWindow, - AccessibilityTree, -} from "./interface"; - -export * from "./helpers"; +// Expore classes +export * from "./computer"; diff --git a/libs/computer/typescript/src/interface/base.ts b/libs/computer/typescript/src/interface/base.ts deleted file mode 100644 index 9ab3fd0e..00000000 --- a/libs/computer/typescript/src/interface/base.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { KeyType, MouseButton, AccessibilityTree } from "./types"; - -/** - * Base interface for computer control implementations. - */ -export interface BaseComputerInterface { - /** - * Wait for the interface to be ready. - */ - waitForReady(): Promise; - - /** - * Get a screenshot of the current screen. - */ - getScreenshot(): Promise; - - /** - * Move the mouse to the specified coordinates. - */ - moveMouse(x: number, y: number): Promise; - - /** - * Click the mouse at the current position. - */ - click(button?: MouseButton): Promise; - - /** - * Type text at the current cursor position. - */ - typeText(text: string): Promise; - - /** - * Press a key. - */ - pressKey(key: KeyType): Promise; - - /** - * Get the accessibility tree. - */ - getAccessibilityTree(): Promise; -} diff --git a/libs/computer/typescript/src/interface/factory.ts b/libs/computer/typescript/src/interface/factory.ts deleted file mode 100644 index 8f3fffba..00000000 --- a/libs/computer/typescript/src/interface/factory.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type { BaseComputerInterface } from './base'; -import type { OSType } from '../computer'; - -export interface InterfaceOptions { - ipAddress: string; - apiKey?: string; - vmName?: string; -} - -/** - * Factory for creating OS-specific computer interfaces. - */ -export class InterfaceFactory { - /** - * Create an interface for the specified OS. - * - * @param os The operating system type ('macos', 'linux', 'windows') - * @param ipAddress The IP address to connect to - * @param apiKey Optional API key for cloud providers - * @param vmName Optional VM name for cloud providers - * @returns An instance of the appropriate computer interface - */ - static createInterfaceForOS( - os: OSType, - ipAddress: string, - apiKey?: string, - vmName?: string - ): BaseComputerInterface { - const options: InterfaceOptions = { - ipAddress, - apiKey, - vmName - }; - - switch (os) { - case 'macos': - // Dynamic import would be used in real implementation - // TODO: Implement macOS interface - throw new Error('macOS interface not yet implemented'); - - case 'linux': - // Dynamic import would be used in real implementation - // TODO: Implement Linux interface - throw new Error('Linux interface not yet implemented'); - - case 'windows': - // Dynamic import would be used in real implementation - // TODO: Implement Windows interface - throw new Error('Windows interface not yet implemented'); - - default: - // TODO: Implement interface for this OS - throw new Error(`Interface for OS ${os} not implemented`); - } - } -} diff --git a/libs/computer/typescript/src/interface/index.ts b/libs/computer/typescript/src/interface/index.ts deleted file mode 100644 index 38e3fd04..00000000 --- a/libs/computer/typescript/src/interface/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from "./base"; -export * from "./factory"; -export * from "./types"; -export * from "./macos"; -export * from "./linux"; diff --git a/libs/computer/typescript/src/interface/linux.ts b/libs/computer/typescript/src/interface/linux.ts deleted file mode 100644 index 12e67988..00000000 --- a/libs/computer/typescript/src/interface/linux.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { BaseComputerInterface } from "./base"; -import type { KeyType, MouseButton, AccessibilityTree } from "./types"; - -/** - * Linux-specific implementation of the computer interface. - */ -export class LinuxComputerInterface implements BaseComputerInterface { - private ip_address: string; - - constructor(ip_address: string) { - this.ip_address = ip_address; - } - - async waitForReady(): Promise { - // Implementation will go here - } - - async getScreenshot(): Promise { - // Implementation will go here - return Buffer.from([]); - } - - async moveMouse(x: number, y: number): Promise { - // Implementation will go here - } - - async click(button: MouseButton = "left"): Promise { - // Implementation will go here - } - - async typeText(text: string): Promise { - // Implementation will go here - } - - async pressKey(key: KeyType): Promise { - // Implementation will go here - } - - async getAccessibilityTree(): Promise { - // Implementation will go here - return { - success: false, - frontmost_application: "", - windows: [], - }; - } -} diff --git a/libs/computer/typescript/src/interface/macos.ts b/libs/computer/typescript/src/interface/macos.ts deleted file mode 100644 index 93e2688d..00000000 --- a/libs/computer/typescript/src/interface/macos.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { BaseComputerInterface } from "./base"; -import type { KeyType, MouseButton, AccessibilityTree } from "./types"; - -/** - * macOS-specific implementation of the computer interface. - */ -export class MacOSComputerInterface implements BaseComputerInterface { - private ip_address: string; - - constructor(ip_address: string) { - this.ip_address = ip_address; - } - - async waitForReady(): Promise { - // Implementation will go here - } - - async getScreenshot(): Promise { - // Implementation will go here - return Buffer.from([]); - } - - async moveMouse(x: number, y: number): Promise { - // Implementation will go here - } - - async click(button: MouseButton = "left"): Promise { - // Implementation will go here - } - - async typeText(text: string): Promise { - // Implementation will go here - } - - async pressKey(key: KeyType): Promise { - // Implementation will go here - } - - async getAccessibilityTree(): Promise { - // Implementation will go here - return { - success: false, - frontmost_application: "", - windows: [], - }; - } -} diff --git a/libs/computer/typescript/src/interface/types.ts b/libs/computer/typescript/src/interface/types.ts deleted file mode 100644 index 80f2b0a7..00000000 --- a/libs/computer/typescript/src/interface/types.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Navigation key literals - */ -export type NavigationKey = 'pagedown' | 'pageup' | 'home' | 'end' | 'left' | 'right' | 'up' | 'down'; - -/** - * Special key literals - */ -export type SpecialKey = 'enter' | 'esc' | 'tab' | 'space' | 'backspace' | 'del'; - -/** - * Modifier key literals - */ -export type ModifierKey = 'ctrl' | 'alt' | 'shift' | 'win' | 'command' | 'option'; - -/** - * Function key literals - */ -export type FunctionKey = 'f1' | 'f2' | 'f3' | 'f4' | 'f5' | 'f6' | 'f7' | 'f8' | 'f9' | 'f10' | 'f11' | 'f12'; - -/** - * Keyboard keys that can be used with press_key. - */ -export enum Key { - // Navigation - PAGE_DOWN = 'pagedown', - PAGE_UP = 'pageup', - HOME = 'home', - END = 'end', - LEFT = 'left', - RIGHT = 'right', - UP = 'up', - DOWN = 'down', - - // Special keys - RETURN = 'enter', - ENTER = 'enter', - ESCAPE = 'esc', - ESC = 'esc', - TAB = 'tab', - SPACE = 'space', - BACKSPACE = 'backspace', - DELETE = 'del', - - // Modifier keys - ALT = 'alt', - CTRL = 'ctrl', - SHIFT = 'shift', - WIN = 'win', - COMMAND = 'command', - OPTION = 'option', - - // Function keys - F1 = 'f1', - F2 = 'f2', - F3 = 'f3', - F4 = 'f4', - F5 = 'f5', - F6 = 'f6', - F7 = 'f7', - F8 = 'f8', - F9 = 'f9', - F10 = 'f10', - F11 = 'f11', - F12 = 'f12' -} - -/** - * Combined key type - */ -export type KeyType = Key | NavigationKey | SpecialKey | ModifierKey | FunctionKey | string; - -/** - * Key type for mouse actions - */ -export type MouseButton = 'left' | 'right' | 'middle'; - -/** - * Information about a window in the accessibility tree. - */ -export interface AccessibilityWindow { - app_name: string; - pid: number; - frontmost: boolean; - has_windows: boolean; - windows: Array>; -} - -/** - * Complete accessibility tree information. - */ -export interface AccessibilityTree { - success: boolean; - frontmost_application: string; - windows: AccessibilityWindow[]; -} diff --git a/libs/computer/typescript/src/providers/base.ts b/libs/computer/typescript/src/providers/base.ts deleted file mode 100644 index d5368a93..00000000 --- a/libs/computer/typescript/src/providers/base.ts +++ /dev/null @@ -1,140 +0,0 @@ -/** - * Types of VM providers available. - */ -export enum VMProviderType { - LUME = 'lume', - LUMIER = 'lumier', - CLOUD = 'cloud', - UNKNOWN = 'unknown' -} - -/** - * Base interface for VM providers. - * All VM provider implementations must implement this interface. - */ -export interface BaseVMProvider { - /** - * Get the provider type. - */ - readonly providerType: VMProviderType; - - /** - * Get VM information by name. - * - * @param name Name of the VM to get information for - * @param storage Optional storage path override - * @returns Dictionary with VM information including status, IP address, etc. - */ - getVM(name: string, storage?: string): Promise>; - - /** - * List all available VMs. - */ - listVMs(): Promise>>; - - /** - * Run a VM by name with the given options. - * - * @param image VM image to run - * @param name Name for the VM - * @param runOpts Run options for the VM - * @param storage Optional storage path - * @returns VM run response - */ - runVM( - image: string, - name: string, - runOpts: Record, - storage?: string - ): Promise>; - - /** - * Stop a VM by name. - * - * @param name Name of the VM to stop - * @param storage Optional storage path - */ - stopVM(name: string, storage?: string): Promise; - - /** - * Get the IP address of a VM. - * - * @param name Name of the VM - * @param storage Optional storage path - * @param retryDelay Delay between retries in seconds - * @returns IP address of the VM - */ - getIP(name: string, storage?: string, retryDelay?: number): Promise; - - /** - * Update VM settings. - * - * @param name Name of the VM - * @param cpu New CPU allocation - * @param memory New memory allocation - * @param storage Optional storage path - */ - updateVM( - name: string, - cpu?: number, - memory?: string, - storage?: string - ): Promise; - - /** - * Context manager enter method - */ - __aenter__(): Promise; - - /** - * Context manager exit method - */ - __aexit__( - excType: any, - excVal: any, - excTb: any - ): Promise; -} - -/** - * Abstract base class for VM providers that implements context manager - */ -export abstract class BaseVMProviderImpl implements BaseVMProvider { - abstract readonly providerType: VMProviderType; - - abstract getVM(name: string, storage?: string): Promise>; - abstract listVMs(): Promise>>; - abstract runVM( - image: string, - name: string, - runOpts: Record, - storage?: string - ): Promise>; - abstract stopVM(name: string, storage?: string): Promise; - abstract getIP(name: string, storage?: string, retryDelay?: number): Promise; - abstract updateVM( - name: string, - cpu?: number, - memory?: string, - storage?: string - ): Promise; - - async __aenter__(): Promise { - // Default implementation - can be overridden - return this; - } - - /** - * Async context manager exit. - * - * This method is called when exiting an async context manager block. - * It handles proper cleanup of resources, including stopping any running containers. - */ - async __aexit__( - _excType: any, - _excVal: any, - _excTb: any - ): Promise { - // Default implementation - can be overridden - } -} diff --git a/libs/computer/typescript/src/providers/cloud/index.ts b/libs/computer/typescript/src/providers/cloud/index.ts deleted file mode 100644 index 6b434c36..00000000 --- a/libs/computer/typescript/src/providers/cloud/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -/** - * Cloud VM provider implementation. - */ - -export { CloudProvider } from "./provider"; diff --git a/libs/computer/typescript/src/providers/cloud/provider.ts b/libs/computer/typescript/src/providers/cloud/provider.ts deleted file mode 100644 index 573f699c..00000000 --- a/libs/computer/typescript/src/providers/cloud/provider.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Cloud VM provider implementation. - * - * This provider is a placeholder for cloud-based VM provisioning. - * It will be implemented to support cloud VM services in the future. - */ - -import { BaseVMProviderImpl, VMProviderType } from '../base'; - -export interface CloudProviderOptions { - verbose?: boolean; - apiKey?: string; - region?: string; - [key: string]: any; -} - -export class CloudProvider extends BaseVMProviderImpl { - readonly providerType = VMProviderType.CLOUD; - - private verbose: boolean; - private options: CloudProviderOptions; - - constructor(options: CloudProviderOptions = {}) { - super(); - this.verbose = options.verbose || false; - this.options = options; - } - - // TODO: Implement getVM for cloud provider - async getVM(name: string, storage?: string): Promise> { - throw new Error('CloudProvider is not fully implemented yet. Please use LUME or LUMIER provider instead.'); - } - - // TODO: Implement listVMs for cloud provider - async listVMs(): Promise>> { - throw new Error('CloudProvider is not fully implemented yet. Please use LUME or LUMIER provider instead.'); - } - - // TODO: Implement runVM for cloud provider - async runVM( - image: string, - name: string, - runOpts: Record, - storage?: string - ): Promise> { - throw new Error('CloudProvider is not fully implemented yet. Please use LUME or LUMIER provider instead.'); - } - - // TODO: Implement stopVM for cloud provider - async stopVM(name: string, storage?: string): Promise { - throw new Error('CloudProvider is not fully implemented yet. Please use LUME or LUMIER provider instead.'); - } - - // TODO: Implement getIP for cloud provider - async getIP(name: string, storage?: string, retryDelay?: number): Promise { - throw new Error('CloudProvider is not fully implemented yet. Please use LUME or LUMIER provider instead.'); - } - - // TODO: Implement updateVM for cloud provider - async updateVM( - name: string, - cpu?: number, - memory?: string, - storage?: string - ): Promise { - throw new Error('CloudProvider is not fully implemented yet. Please use LUME or LUMIER provider instead.'); - } -} diff --git a/libs/computer/typescript/src/providers/factory.ts b/libs/computer/typescript/src/providers/factory.ts deleted file mode 100644 index a2efb149..00000000 --- a/libs/computer/typescript/src/providers/factory.ts +++ /dev/null @@ -1,150 +0,0 @@ -/** - * Factory for creating VM providers. - */ - -import type { BaseVMProvider } from './base'; -import { VMProviderType } from './base'; - -export interface VMProviderOptions { - port?: number; - host?: string; - binPath?: string; - storage?: string; - sharedPath?: string; - image?: string; - verbose?: boolean; - ephemeral?: boolean; - noVNCPort?: number; - [key: string]: any; // Allow additional provider-specific options -} - -export class VMProviderFactory { - /** - * Create a VM provider instance based on the provider type. - * - * @param providerType The type of provider to create - * @param options Provider-specific options - * @returns The created VM provider instance - * @throws Error if the provider type is not supported or dependencies are missing - */ - static async createProvider( - providerType: VMProviderType | string, - options: VMProviderOptions = {} - ): Promise { - // Convert string to enum if needed - let type: VMProviderType; - if (typeof providerType === 'string') { - const normalizedType = providerType.toLowerCase(); - if (Object.values(VMProviderType).includes(normalizedType as VMProviderType)) { - type = normalizedType as VMProviderType; - } else { - type = VMProviderType.UNKNOWN; - } - } else { - type = providerType; - } - - // Extract common options with defaults - const { - port = 7777, - host = 'localhost', - binPath, - storage, - sharedPath, - image, - verbose = false, - ephemeral = false, - noVNCPort, - ...additionalOptions - } = options; - - switch (type) { - case VMProviderType.LUME: { - try { - // Dynamic import for Lume provider - const { LumeProvider, HAS_LUME } = await import('./lume'); - - if (!HAS_LUME) { - throw new Error( - 'The required dependencies for LumeProvider are not available. ' + - 'Please ensure curl is installed and in your PATH.' - ); - } - - return new LumeProvider({ - port, - host, - storage, - verbose, - ephemeral - }); - } catch (error) { - if (error instanceof Error && error.message.includes('Cannot find module')) { - throw new Error( - 'The LumeProvider module is not available. ' + - 'Please install it with: npm install @cua/computer-lume' - ); - } - throw error; - } - } - - case VMProviderType.LUMIER: { - try { - // Dynamic import for Lumier provider - const { LumierProvider, HAS_LUMIER } = await import('./lumier'); - - if (!HAS_LUMIER) { - throw new Error( - 'Docker is required for LumierProvider. ' + - 'Please install Docker for Apple Silicon and Lume CLI before using this provider.' - ); - } - - return new LumierProvider({ - port, - host, - storage, - sharedPath, - image: image || 'macos-sequoia-cua:latest', - verbose, - ephemeral, - noVNCPort - }); - } catch (error) { - if (error instanceof Error && error.message.includes('Cannot find module')) { - throw new Error( - 'The LumierProvider module is not available. ' + - 'Docker and Lume CLI are required for LumierProvider. ' + - 'Please install Docker for Apple Silicon and run the Lume installer script.' - ); - } - throw error; - } - } - - case VMProviderType.CLOUD: { - try { - // Dynamic import for Cloud provider - const { CloudProvider } = await import('./cloud'); - - return new CloudProvider({ - verbose, - ...additionalOptions - }); - } catch (error) { - if (error instanceof Error && error.message.includes('Cannot find module')) { - throw new Error( - 'The CloudProvider is not fully implemented yet. ' + - 'Please use LUME or LUMIER provider instead.' - ); - } - throw error; - } - } - - default: - throw new Error(`Unsupported provider type: ${providerType}`); - } - } -} diff --git a/libs/computer/typescript/src/providers/index.ts b/libs/computer/typescript/src/providers/index.ts deleted file mode 100644 index 078b4cbd..00000000 --- a/libs/computer/typescript/src/providers/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Export all provider-related modules. - */ - -export * from './base'; -export * from './factory'; -export * from './lume_api'; - -// Export provider implementations -export * from './lume'; -export * from './lumier'; -export * from './cloud'; diff --git a/libs/computer/typescript/src/providers/lume/index.ts b/libs/computer/typescript/src/providers/lume/index.ts deleted file mode 100644 index 9b020d8e..00000000 --- a/libs/computer/typescript/src/providers/lume/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Lume VM provider implementation. - */ - -export let HAS_LUME = false; - -try { - // Check if curl is available - const { execSync } = require('child_process'); - execSync('which curl', { stdio: 'ignore' }); - HAS_LUME = true; -} catch { - HAS_LUME = false; -} - -export { LumeProvider } from './provider'; diff --git a/libs/computer/typescript/src/providers/lume/provider.ts b/libs/computer/typescript/src/providers/lume/provider.ts deleted file mode 100644 index 7c8c980a..00000000 --- a/libs/computer/typescript/src/providers/lume/provider.ts +++ /dev/null @@ -1,182 +0,0 @@ -/** - * Lume VM provider implementation using curl commands. - * - * This provider uses direct curl commands to interact with the Lume API, - * removing the dependency on the pylume Python package. - */ - -import { BaseVMProviderImpl, VMProviderType } from '../base'; -import type { LumeRunOptions } from '../lume_api'; -import { - HAS_CURL, - lumeApiGet, - lumeApiRun, - lumeApiStop, - lumeApiUpdate, - lumeApiPull, - parseMemory -} from '../lume_api'; - -export interface LumeProviderOptions { - port?: number; - host?: string; - storage?: string; - verbose?: boolean; - ephemeral?: boolean; -} - -export class LumeProvider extends BaseVMProviderImpl { - readonly providerType = VMProviderType.LUME; - - private host: string; - private port: number; - private storage?: string; - private verbose: boolean; - private ephemeral: boolean; - - constructor(options: LumeProviderOptions = {}) { - super(); - - if (!HAS_CURL) { - throw new Error( - 'curl is required for LumeProvider. ' + - 'Please ensure it is installed and in your PATH.' - ); - } - - this.host = options.host || 'localhost'; - this.port = options.port || 7777; - this.storage = options.storage; - this.verbose = options.verbose || false; - this.ephemeral = options.ephemeral || false; - } - - async getVM(name: string, storage?: string): Promise> { - return lumeApiGet( - name, - storage || this.storage, - this.host, - this.port, - this.verbose - ); - } - - async listVMs(): Promise>> { - const response = await lumeApiGet( - '', - this.storage, - this.host, - this.port, - this.verbose - ); - - // The response should be an array of VMs - if (Array.isArray(response)) { - return response; - } - - // If it's an object with a vms property - if (response.vms && Array.isArray(response.vms)) { - return response.vms; - } - - // Otherwise return empty array - return []; - } - - async runVM( - image: string, - name: string, - runOpts: LumeRunOptions, - storage?: string - ): Promise> { - // Ensure the image is available - if (this.verbose) { - console.log(`Pulling image ${image} if needed...`); - } - - try { - await lumeApiPull(image, this.host, this.port, this.verbose); - } catch (error) { - if (this.verbose) { - console.log(`Failed to pull image: ${error}`); - } - } - - // Run the VM - return lumeApiRun( - image, - name, - runOpts, - storage || this.storage, - this.host, - this.port, - this.verbose - ); - } - - async stopVM(name: string, storage?: string): Promise { - await lumeApiStop( - name, - storage || this.storage, - this.host, - this.port, - this.verbose - ); - - // If ephemeral, the VM should be automatically deleted after stopping - if (this.ephemeral && this.verbose) { - console.log(`VM ${name} stopped and removed (ephemeral mode)`); - } - } - - async getIP(name: string, storage?: string, retryDelay: number = 1): Promise { - const maxRetries = 30; - let retries = 0; - - while (retries < maxRetries) { - try { - const vmInfo = await this.getVM(name, storage); - - if (vmInfo.ip && vmInfo.ip !== '') { - return vmInfo.ip; - } - - if (vmInfo.status === 'stopped' || vmInfo.status === 'error') { - throw new Error(`VM ${name} is in ${vmInfo.status} state`); - } - } catch (error) { - if (retries === maxRetries - 1) { - throw error; - } - } - - retries++; - await new Promise(resolve => setTimeout(resolve, retryDelay * 1000)); - } - - throw new Error(`Failed to get IP for VM ${name} after ${maxRetries} retries`); - } - - async updateVM( - name: string, - cpu?: number, - memory?: string, - storage?: string - ): Promise { - // Validate memory format if provided - if (memory) { - parseMemory(memory); // This will throw if invalid - } - - await lumeApiUpdate( - name, - cpu, - memory, - storage || this.storage, - this.host, - this.port, - this.verbose - ); - } -} diff --git a/libs/computer/typescript/src/providers/lume_api.ts b/libs/computer/typescript/src/providers/lume_api.ts deleted file mode 100644 index c198583d..00000000 --- a/libs/computer/typescript/src/providers/lume_api.ts +++ /dev/null @@ -1,265 +0,0 @@ -/** - * Lume API utilities for interacting with the Lume VM API. - * This module provides low-level API functions used by the Lume provider. - */ - -import { exec, execSync } from "child_process"; -import { promisify } from "util"; - -const execAsync = promisify(exec); - -export let HAS_CURL = false; - -// Check for curl availability -try { - execSync("which curl", { stdio: "ignore" }); - HAS_CURL = true; -} catch { - HAS_CURL = false; -} - -/** - * Parse memory string to bytes. - * Supports formats like "2GB", "512MB", "1024KB", etc. - * Defaults to 1GB - */ -export function parseMemory(memory = "1GB"): number { - const match = memory.match(/^(\d+(?:\.\d+)?)\s*([KMGT]?B?)$/i); - if (!match) { - throw new Error(`Invalid memory format: ${memory}`); - } - - const value = parseFloat(match[1]!); - const unit = match[2]!.toUpperCase(); - - const multipliers: Record = { - B: 1, - KB: 1024, - MB: 1024 * 1024, - GB: 1024 * 1024 * 1024, - TB: 1024 * 1024 * 1024 * 1024, - K: 1024, - M: 1024 * 1024, - G: 1024 * 1024 * 1024, - T: 1024 * 1024 * 1024 * 1024, - }; - - return Math.floor(value * (multipliers[unit] || 1)); -} - -/** - * Execute a curl command and return the result. - */ -async function executeCurl( - command: string -): Promise<{ stdout: string; stderr: string }> { - try { - const { stdout, stderr } = await execAsync(command); - return { stdout, stderr }; - } catch (error: any) { - throw new Error(`Curl command failed: ${error.message}`); - } -} - -/** - * Get VM information using Lume API. - */ -export async function lumeApiGet( - vmName: string = "", - storage?: string, - host: string = "localhost", - port: number = 7777, - debug: boolean = false -): Promise> { - let url = `http://${host}:${port}/vms`; - if (vmName) { - url += `/${encodeURIComponent(vmName)}`; - } - - const params = new URLSearchParams(); - if (storage) { - params.append("storage", storage); - } - - if (params.toString()) { - url += `?${params.toString()}`; - } - - const command = `curl -s -X GET "${url}"`; - - if (debug) { - console.log(`Executing: ${command}`); - } - - const { stdout } = await executeCurl(command); - - try { - return JSON.parse(stdout); - } catch (error) { - throw new Error(`Failed to parse API response: ${stdout}`); - } -} - -/** - * Options for running a VM using the Lume API. - */ -export interface LumeRunOptions { - /** CPU cores to allocate to the VM */ - cpu?: number; - /** Memory to allocate to the VM (e.g., "8GB", "512MB") */ - memory?: string; - /** Display configuration for the VM */ - display?: { - width?: number; - height?: number; - dpi?: number; - color_depth?: number; - }; - /** Environment variables to set in the VM */ - env?: Record; - /** Directories to share with the VM */ - shared_directories?: Record; - /** Network configuration */ - network?: { - type?: string; - bridge?: string; - nat?: boolean; - }; - /** Whether to run the VM in headless mode */ - headless?: boolean; - /** Whether to enable GPU acceleration */ - gpu?: boolean; - /** Storage location for the VM */ - storage?: string; - /** Custom VM configuration options */ - vm_options?: Record; - /** Additional provider-specific options */ - [key: string]: any; -} - -/** - * Run a VM using Lume API. - */ -export async function lumeApiRun( - image: string, - name: string, - runOpts: LumeRunOptions, - storage?: string, - host: string = "localhost", - port: number = 7777, - debug: boolean = false -): Promise> { - const url = `http://${host}:${port}/vms/run`; - - const body: LumeRunOptions = { - image, - name, - ...runOpts, - }; - - if (storage) { - body.storage = storage; - } - - const command = `curl -s -X POST "${url}" -H "Content-Type: application/json" -d '${JSON.stringify( - body - )}'`; - - if (debug) { - console.log(`Executing: ${command}`); - } - - const { stdout } = await executeCurl(command); - - try { - return JSON.parse(stdout); - } catch (error) { - throw new Error(`Failed to parse API response: ${stdout}`); - } -} - -/** - * Stop a VM using Lume API. - */ -export async function lumeApiStop( - vmName: string, - storage?: string, - host: string = "localhost", - port: number = 7777, - debug: boolean = false -): Promise { - const url = `http://${host}:${port}/vms/${encodeURIComponent(vmName)}/stop`; - - const params = new URLSearchParams(); - if (storage) { - params.append("storage", storage); - } - - const fullUrl = params.toString() ? `${url}?${params.toString()}` : url; - const command = `curl -s -X POST "${fullUrl}"`; - - if (debug) { - console.log(`Executing: ${command}`); - } - - await executeCurl(command); -} - -/** - * Update VM settings using Lume API. - */ -export async function lumeApiUpdate( - vmName: string, - cpu?: number, - memory?: string, - storage?: string, - host: string = "localhost", - port: number = 7777, - debug: boolean = false -): Promise { - const url = `http://${host}:${port}/vms/${encodeURIComponent(vmName)}/update`; - - const body: LumeRunOptions = {}; - if (cpu !== undefined) { - body.cpu = cpu; - } - if (memory !== undefined) { - body.memory = memory; - } - if (storage) { - body.storage = storage; - } - - const command = `curl -s -X POST "${url}" -H "Content-Type: application/json" -d '${JSON.stringify( - body - )}'`; - - if (debug) { - console.log(`Executing: ${command}`); - } - - await executeCurl(command); -} - -/** - * Pull a VM image using Lume API. - */ -export async function lumeApiPull( - image: string, - host: string = "localhost", - port: number = 7777, - debug: boolean = false -): Promise { - const url = `http://${host}:${port}/images/pull`; - - const body: LumeRunOptions = { image }; - const command = `curl -s -X POST "${url}" -H "Content-Type: application/json" -d '${JSON.stringify( - body - )}'`; - - if (debug) { - console.log(`Executing: ${command}`); - } - - await executeCurl(command); -} diff --git a/libs/computer/typescript/src/providers/lumier/index.ts b/libs/computer/typescript/src/providers/lumier/index.ts deleted file mode 100644 index 33345619..00000000 --- a/libs/computer/typescript/src/providers/lumier/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Lumier VM provider implementation. - */ - -export let HAS_LUMIER = false; - -try { - // Check if Docker is available - const { execSync } = require("child_process"); - execSync("which docker", { stdio: "ignore" }); - HAS_LUMIER = true; -} catch { - HAS_LUMIER = false; -} - -export { LumierProvider } from "./provider"; diff --git a/libs/computer/typescript/src/providers/lumier/provider.ts b/libs/computer/typescript/src/providers/lumier/provider.ts deleted file mode 100644 index b9a855a1..00000000 --- a/libs/computer/typescript/src/providers/lumier/provider.ts +++ /dev/null @@ -1,401 +0,0 @@ -/** - * Lumier VM provider implementation. - * - * This provider uses Docker containers running the Lumier image to create - * macOS and Linux VMs. It handles VM lifecycle operations through Docker - * commands and container management. - */ - -import { exec } from "child_process"; -import { promisify } from "util"; -import { BaseVMProviderImpl, VMProviderType } from "../base"; -import { - lumeApiGet, - lumeApiRun, - lumeApiStop, - lumeApiUpdate, -} from "../lume_api"; - -const execAsync = promisify(exec); - -export interface LumierProviderOptions { - port?: number; - host?: string; - storage?: string; - sharedPath?: string; - image?: string; - verbose?: boolean; - ephemeral?: boolean; - noVNCPort?: number; -} - -export class LumierProvider extends BaseVMProviderImpl { - readonly providerType = VMProviderType.LUMIER; - - private host: string; - private apiPort: number; - private vncPort?: number; - private ephemeral: boolean; - private storage?: string; - private sharedPath?: string; - private image: string; - private verbose: boolean; - private containerName?: string; - private containerId?: string; - - constructor(options: LumierProviderOptions = {}) { - super(); - - this.host = options.host || "localhost"; - this.apiPort = options.port || 7777; - this.vncPort = options.noVNCPort; - this.ephemeral = options.ephemeral || false; - - // Handle ephemeral storage - if (this.ephemeral) { - this.storage = "ephemeral"; - } else { - this.storage = options.storage; - } - - this.sharedPath = options.sharedPath; - this.image = options.image || "macos-sequoia-cua:latest"; - this.verbose = options.verbose || false; - } - - /** - * Parse memory string to MB integer. - */ - private parseMemory(memoryStr: string | number): number { - if (typeof memoryStr === "number") { - return memoryStr; - } - - const match = memoryStr.match(/^(\d+)([A-Za-z]*)$/); - if (match) { - const value = parseInt(match[1]); - const unit = match[2].toUpperCase(); - - if (unit === "GB" || unit === "G") { - return value * 1024; - } else if (unit === "MB" || unit === "M" || unit === "") { - return value; - } - } - - console.warn( - `Could not parse memory string '${memoryStr}', using 8GB default` - ); - return 8192; // Default to 8GB - } - - /** - * Check if a Docker container exists. - */ - private async containerExists(name: string): Promise { - try { - await execAsync(`docker inspect ${name}`); - return true; - } catch { - return false; - } - } - - /** - * Get container status. - */ - private async getContainerStatus(name: string): Promise { - try { - const { stdout } = await execAsync( - `docker inspect -f '{{.State.Status}}' ${name}` - ); - return stdout.trim(); - } catch { - return "not_found"; - } - } - - /** - * Start the Lumier container. - */ - private async startContainer( - name: string, - cpu: number = 4, - memory: string = "8GB", - runOpts: Record = {} - ): Promise { - const memoryMB = this.parseMemory(memory); - - // Build Docker run command - let dockerCmd = `docker run -d --name ${name}`; - - // Add resource limits - dockerCmd += ` --cpus=${cpu}`; - dockerCmd += ` --memory=${memoryMB}m`; - - // Add port mappings - dockerCmd += ` -p ${this.apiPort}:7777`; - if (this.vncPort) { - dockerCmd += ` -p ${this.vncPort}:8006`; - } - - // Add storage volume if not ephemeral - if (this.storage && this.storage !== "ephemeral") { - dockerCmd += ` -v ${this.storage}:/storage`; - } - - // Add shared path if specified - if (this.sharedPath) { - dockerCmd += ` -v ${this.sharedPath}:/shared`; - } - - // Add environment variables - if (runOpts.env) { - for (const [key, value] of Object.entries(runOpts.env)) { - dockerCmd += ` -e ${key}=${value}`; - } - } - - // Add the image - dockerCmd += ` ${this.image}`; - - if (this.verbose) { - console.log(`Starting container with command: ${dockerCmd}`); - } - - try { - const { stdout } = await execAsync(dockerCmd); - this.containerId = stdout.trim(); - this.containerName = name; - } catch (error: any) { - throw new Error(`Failed to start container: ${error.message}`); - } - } - - /** - * Wait for the API to be ready. - */ - private async waitForAPI(maxRetries: number = 30): Promise { - for (let i = 0; i < maxRetries; i++) { - try { - await lumeApiGet("", undefined, this.host, this.apiPort, false); - return; - } catch { - if (i === maxRetries - 1) { - throw new Error("API failed to become ready"); - } - await new Promise((resolve) => setTimeout(resolve, 1000)); - } - } - } - - async getVM(name: string, storage?: string): Promise> { - // First check if container exists - const containerStatus = await this.getContainerStatus( - this.containerName || name - ); - - if (containerStatus === "not_found") { - throw new Error(`Container ${name} not found`); - } - - // If container is not running, return basic status - if (containerStatus !== "running") { - return { - name, - status: containerStatus, - ip: "", - cpu: 0, - memory: "0MB", - }; - } - - // Get VM info from API - return lumeApiGet( - name, - storage || this.storage, - this.host, - this.apiPort, - this.verbose - ); - } - - async listVMs(): Promise>> { - // Check if our container is running - if (!this.containerName) { - return []; - } - - const containerStatus = await this.getContainerStatus(this.containerName); - if (containerStatus !== "running") { - return []; - } - - // Get VMs from API - const response = await lumeApiGet( - "", - this.storage, - this.host, - this.apiPort, - this.verbose - ); - - if (Array.isArray(response)) { - return response; - } - - if (response.vms && Array.isArray(response.vms)) { - return response.vms; - } - - return []; - } - - async runVM( - image: string, - name: string, - runOpts: Record, - storage?: string - ): Promise> { - // Check if container already exists - const exists = await this.containerExists(name); - - if (exists) { - const status = await this.getContainerStatus(name); - if (status === "running") { - // Container already running, just run VM through API - return lumeApiRun( - image, - name, - runOpts, - storage || this.storage, - this.host, - this.apiPort, - this.verbose - ); - } else { - // Start existing container - await execAsync(`docker start ${name}`); - } - } else { - // Create and start new container - await this.startContainer( - name, - runOpts.cpu || 4, - runOpts.memory || "8GB", - runOpts - ); - } - - // Wait for API to be ready - await this.waitForAPI(); - - // Run VM through API - return lumeApiRun( - image, - name, - runOpts, - storage || this.storage, - this.host, - this.apiPort, - this.verbose - ); - } - - async stopVM(name: string, storage?: string): Promise { - // First stop VM through API - try { - await lumeApiStop( - name, - storage || this.storage, - this.host, - this.apiPort, - this.verbose - ); - } catch (error) { - if (this.verbose) { - console.log(`Failed to stop VM through API: ${error}`); - } - } - - // Stop the container - if (this.containerName) { - try { - await execAsync(`docker stop ${this.containerName}`); - - // Remove container if ephemeral - if (this.ephemeral) { - await execAsync(`docker rm ${this.containerName}`); - if (this.verbose) { - console.log( - `Container ${this.containerName} stopped and removed (ephemeral mode)` - ); - } - } - } catch (error: any) { - throw new Error(`Failed to stop container: ${error.message}`); - } - } - } - - async getIP( - name: string, - storage?: string, - retryDelay: number = 1 - ): Promise { - const maxRetries = 30; - - for (let i = 0; i < maxRetries; i++) { - try { - const vmInfo = await this.getVM(name, storage); - - if (vmInfo.ip && vmInfo.ip !== "") { - return vmInfo.ip; - } - - if (vmInfo.status === "stopped" || vmInfo.status === "error") { - throw new Error(`VM ${name} is in ${vmInfo.status} state`); - } - } catch (error) { - if (i === maxRetries - 1) { - throw error; - } - } - - await new Promise((resolve) => setTimeout(resolve, retryDelay * 1000)); - } - - throw new Error( - `Failed to get IP for VM ${name} after ${maxRetries} retries` - ); - } - - async updateVM( - name: string, - cpu?: number, - memory?: string, - storage?: string - ): Promise { - await lumeApiUpdate( - name, - cpu, - memory, - storage || this.storage, - this.host, - this.apiPort, - this.verbose - ); - } - - async __aexit__(excType: any, excVal: any, excTb: any): Promise { - // Clean up container if ephemeral - if (this.ephemeral && this.containerName) { - try { - await execAsync(`docker stop ${this.containerName}`); - await execAsync(`docker rm ${this.containerName}`); - } catch { - // Ignore errors during cleanup - } - } - } -} diff --git a/libs/computer/typescript/src/telemetry.ts b/libs/computer/typescript/src/telemetry.ts deleted file mode 100644 index 2cd193de..00000000 --- a/libs/computer/typescript/src/telemetry.ts +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Telemetry tracking for Computer usage. - */ - -interface TelemetryEvent { - event: string; - timestamp: Date; - properties?: Record; -} - -export class TelemetryManager { - private enabled: boolean = true; - private events: TelemetryEvent[] = []; - - setEnabled(enabled: boolean): void { - this.enabled = enabled; - } - - isEnabled(): boolean { - return this.enabled; - } - - track(event: string, properties?: Record): void { - if (!this.enabled) { - return; - } - - const telemetryEvent: TelemetryEvent = { - event, - timestamp: new Date(), - properties, - }; - - this.events.push(telemetryEvent); - - // In a real implementation, this would send to a telemetry service - // For now, just log to debug - if (process.env.NODE_ENV === "development") { - console.debug("[Telemetry]", event, properties); - } else { - //todo: log telemetry to posthog - } - } - - getEvents(): TelemetryEvent[] { - return [...this.events]; - } - - clear(): void { - this.events = []; - } -} - -// Singleton instance -const telemetryManager = new TelemetryManager(); - -/** - * Record computer initialization event - */ -export function recordComputerInitialization(): void { - telemetryManager.track("computer_initialized", { - timestamp: new Date().toISOString(), - version: process.env.npm_package_version || "unknown", - }); -} - -/** - * Record VM start event - */ -export function recordVMStart(vmName: string, provider: string): void { - telemetryManager.track("vm_started", { - vm_name: vmName, - provider, - timestamp: new Date().toISOString(), - }); -} - -/** - * Record VM stop event - */ -export function recordVMStop(vmName: string, duration: number): void { - telemetryManager.track("vm_stopped", { - vm_name: vmName, - duration_ms: duration, - timestamp: new Date().toISOString(), - }); -} - -/** - * Record interface action - */ -export function recordInterfaceAction( - action: string, - details?: Record -): void { - telemetryManager.track("interface_action", { - action, - ...details, - timestamp: new Date().toISOString(), - }); -} - -/** - * Set telemetry enabled/disabled - */ -export function setTelemetryEnabled(enabled: boolean): void { - telemetryManager.setEnabled(enabled); -} - -/** - * Check if telemetry is enabled - */ -export function isTelemetryEnabled(): boolean { - return telemetryManager.isEnabled(); -} - -export { telemetryManager }; diff --git a/libs/computer/typescript/src/types.ts b/libs/computer/typescript/src/types.ts deleted file mode 100644 index b0749cc5..00000000 --- a/libs/computer/typescript/src/types.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Display configuration for the computer. - */ -export interface Display { - width: number; - height: number; - scale_factor?: number; -} - -/** - * Computer configuration model. - */ -export interface ComputerConfig { - image: string; - tag: string; - name: string; - display: Display; - memory: string; - cpu: string; - vm_provider?: any; // Will be properly typed when implemented -} diff --git a/libs/computer/typescript/src/util/logger.ts b/libs/computer/typescript/src/util/logger.ts new file mode 100644 index 00000000..a3ab86cf --- /dev/null +++ b/libs/computer/typescript/src/util/logger.ts @@ -0,0 +1,7 @@ +/** + * Shared logger module for the computer library + */ +import pino from "pino"; + +// Create and export default loggers for common components +export const computerLogger = pino({ name: "computer" }); diff --git a/libs/computer/typescript/src/util/lume.ts b/libs/computer/typescript/src/util/lume.ts new file mode 100644 index 00000000..cd42e6ba --- /dev/null +++ b/libs/computer/typescript/src/util/lume.ts @@ -0,0 +1,565 @@ +/** + * Shared API utilities for Lume and Lumier providers. + * + * This module contains shared functions for interacting with the Lume API, + * used by both the LumeProvider and LumierProvider classes. + */ + +import pino from "pino"; + +// Setup logging +const logger = pino({ name: "lume_api" }); + +// Types for API responses and options +// These are lume-specific +export interface SharedDirectory { + hostPath: string; + tag: string; + readOnly: boolean; +} + +export interface VMInfo { + status?: string; + name: string; + diskSize: { + allocated: number; + total: number; + }; + memorySize: number; + os: string; + display: string; + locationName: string; + cpuCount?: number; + // started state results + vncUrl?: string; + ipAddress?: string; + sharedDirectories?: SharedDirectory[]; +} + +export interface RunOptions { + [key: string]: any; +} + +export interface UpdateOptions { + [key: string]: any; +} + +/** + * Use fetch to get VM information from Lume API. + * + * @param vmName - Name of the VM to get info for + * @param host - API host + * @param port - API port + * @param storage - Storage path for the VM + * @param debug - Whether to show debug output + * @param verbose - Enable verbose logging + * @returns Dictionary with VM status information parsed from JSON response + */ +export async function lumeApiGet( + vmName: string, + host: string, + port: number, + storage?: string, + debug: boolean = false, + verbose: boolean = false +): Promise { + // URL encode the storage parameter for the query + let storageParam = ""; + + if (storage) { + // First encode the storage path properly + const encodedStorage = encodeURIComponent(storage); + storageParam = `?storage=${encodedStorage}`; + } + + // Construct API URL with encoded storage parameter if needed + const apiUrl = `http://${host}:${port}/lume/vms${vmName ? `/${vmName}` : ""}${storageParam}`; + + // Only print the fetch URL when debug is enabled + logger.info(`Executing API request: ${apiUrl}`); + + try { + // Execute the request with timeouts + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 20000); // 20 second timeout + + const response = await fetch(apiUrl, { + method: "GET", + signal: controller.signal, + }); + + clearTimeout(timeoutId); + + if (!response.ok) { + // Handle HTTP errors + const errorMsg = `HTTP error returned from API server (status: ${response.status})`; + throw new Error(`API request failed: ${errorMsg}`); + } + + // Parse JSON response + // If vmName is provided, API returns a single object; otherwise it returns an array + const data = await response.json(); + const result = vmName ? [data as VMInfo] : (data as VMInfo[]); + + if (debug || verbose) { + console.log(`DEBUG: API response: ${JSON.stringify(result, null, 2)}`); + } + + return result; + } catch (error: any) { + let errorMsg = "Unknown error"; + + if (error.name === "AbortError") { + errorMsg = + "Operation timeout - the API server is taking too long to respond"; + } else if (error.code === "ECONNREFUSED") { + errorMsg = + "Failed to connect to the API server - it might still be starting up"; + } else if (error.code === "ENOTFOUND") { + errorMsg = "Failed to resolve host - check the API server address"; + } else if (error.message) { + errorMsg = error.message; + } + + logger.error(`API request failed: ${errorMsg}`); + throw new Error(`API request failed: ${errorMsg}`); + } +} + +/** + * Run a VM using fetch. + * + * @param vmName - Name of the VM to run + * @param host - API host + * @param port - API port + * @param runOpts - Dictionary of run options + * @param storage - Storage path for the VM + * @param debug - Whether to show debug output + * @param verbose - Enable verbose logging + * @returns Dictionary with API response + */ +export async function lumeApiRun( + vmName: string, + host: string, + port: number, + runOpts: RunOptions, + storage?: string, + debug: boolean = false, + verbose: boolean = false +): Promise { + // URL encode the storage parameter for the query + let storageParam = ""; + + if (storage) { + const encodedStorage = encodeURIComponent(storage); + storageParam = `?storage=${encodedStorage}`; + } + + // Construct API URL + const apiUrl = `http://${host}:${port}/lume/vms/${vmName}/run${storageParam}`; + + // Convert run options to JSON + const jsonData = JSON.stringify(runOpts); + + if (debug || verbose) { + console.log(`Executing fetch API call: POST ${apiUrl}`); + console.log(`Request body: ${jsonData}`); + } + logger.info(`Executing API request: POST ${apiUrl}`); + + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 20000); + + const response = await fetch(apiUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: jsonData, + signal: controller.signal, + }); + + clearTimeout(timeoutId); + + if (!response.ok) { + const errorMsg = `HTTP error returned from API server (status: ${response.status})`; + throw new Error(`API request failed: ${errorMsg}`); + } + + const data = (await response.json()) as VMInfo; + + if (debug || verbose) { + console.log(`DEBUG: API response: ${JSON.stringify(data, null, 2)}`); + } + + return data; + } catch (error: any) { + let errorMsg = "Unknown error"; + + if (error.name === "AbortError") { + errorMsg = + "Operation timeout - the API server is taking too long to respond"; + } else if (error.code === "ECONNREFUSED") { + errorMsg = "Failed to connect to the API server"; + } else if (error.message) { + errorMsg = error.message; + } + + logger.error(`API request failed: ${errorMsg}`); + throw new Error(`API request failed: ${errorMsg}`); + } +} + +/** + * Stop a VM using fetch. + * + * @param vmName - Name of the VM to stop + * @param host - API host + * @param port - API port + * @param storage - Storage path for the VM + * @param debug - Whether to show debug output + * @param verbose - Enable verbose logging + * @returns Dictionary with API response + */ +export async function lumeApiStop( + vmName: string, + host: string, + port: number, + storage?: string, + debug: boolean = false, + verbose: boolean = false +): Promise { + // URL encode the storage parameter for the query + let storageParam = ""; + + if (storage) { + const encodedStorage = encodeURIComponent(storage); + storageParam = `?storage=${encodedStorage}`; + } + + // Construct API URL + const apiUrl = `http://${host}:${port}/lume/vms/${vmName}/stop${storageParam}`; + + if (debug || verbose) { + console.log(`DEBUG: Executing fetch API call: POST ${apiUrl}`); + } + logger.debug(`Executing API request: POST ${apiUrl}`); + + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 20000); + + const response = await fetch(apiUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: "{}", + signal: controller.signal, + }); + + clearTimeout(timeoutId); + + if (!response.ok) { + const errorMsg = `HTTP error returned from API server (status: ${response.status})`; + throw new Error(`API request failed: ${errorMsg}`); + } + + const data = (await response.json()) as VMInfo; + + if (debug || verbose) { + console.log(`DEBUG: API response: ${JSON.stringify(data, null, 2)}`); + } + + return data; + } catch (error: any) { + let errorMsg = "Unknown error"; + + if (error.name === "AbortError") { + errorMsg = + "Operation timeout - the API server is taking too long to respond"; + } else if (error.code === "ECONNREFUSED") { + errorMsg = "Failed to connect to the API server"; + } else if (error.message) { + errorMsg = error.message; + } + + logger.error(`API request failed: ${errorMsg}`); + throw new Error(`API request failed: ${errorMsg}`); + } +} + +/** + * Update VM settings using fetch. + * + * @param vmName - Name of the VM to update + * @param host - API host + * @param port - API port + * @param updateOpts - Dictionary of update options + * @param storage - Storage path for the VM + * @param debug - Whether to show debug output + * @param verbose - Enable verbose logging + * @returns Dictionary with API response + */ +export async function lumeApiUpdate( + vmName: string, + host: string, + port: number, + updateOpts: UpdateOptions, + storage?: string, + debug: boolean = false, + verbose: boolean = false +): Promise { + // URL encode the storage parameter for the query + let storageParam = ""; + + if (storage) { + const encodedStorage = encodeURIComponent(storage); + storageParam = `?storage=${encodedStorage}`; + } + + // Construct API URL + const apiUrl = `http://${host}:${port}/lume/vms/${vmName}/update${storageParam}`; + + // Convert update options to JSON + const jsonData = JSON.stringify(updateOpts); + + if (debug || verbose) { + console.log(`DEBUG: Executing fetch API call: POST ${apiUrl}`); + console.log(`DEBUG: Request body: ${jsonData}`); + } + logger.debug(`Executing API request: POST ${apiUrl}`); + + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 20000); + + const response = await fetch(apiUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: jsonData, + signal: controller.signal, + }); + + clearTimeout(timeoutId); + + if (!response.ok) { + const errorMsg = `HTTP error returned from API server (status: ${response.status})`; + throw new Error(`API request failed: ${errorMsg}`); + } + + const data = (await response.json()) as VMInfo; + + if (debug || verbose) { + console.log(`DEBUG: API response: ${JSON.stringify(data, null, 2)}`); + } + + return data; + } catch (error: any) { + let errorMsg = "Unknown error"; + + if (error.name === "AbortError") { + errorMsg = + "Operation timeout - the API server is taking too long to respond"; + } else if (error.code === "ECONNREFUSED") { + errorMsg = "Failed to connect to the API server"; + } else if (error.message) { + errorMsg = error.message; + } + + logger.error(`API request failed: ${errorMsg}`); + throw new Error(`API request failed: ${errorMsg}`); + } +} + +/** + * Pull a VM image from a registry using fetch. + * + * @param image - Name/tag of the image to pull + * @param name - Name to give the VM after pulling + * @param host - API host + * @param port - API port + * @param storage - Storage path for the VM + * @param registry - Registry to pull from (default: ghcr.io) + * @param organization - Organization in registry (default: trycua) + * @param debug - Whether to show debug output + * @param verbose - Enable verbose logging + * @returns Dictionary with pull status and information + */ +export async function lumeApiPull( + image: string, + name: string, + host: string, + port: number, + storage?: string, + registry: string = "ghcr.io", + organization: string = "trycua", + debug: boolean = false, + verbose: boolean = false +): Promise { + // URL encode the storage parameter for the query + let storageParam = ""; + + if (storage) { + const encodedStorage = encodeURIComponent(storage); + storageParam = `?storage=${encodedStorage}`; + } + + // Construct API URL + const apiUrl = `http://${host}:${port}/lume/pull${storageParam}`; + + // Construct pull options + const pullOpts = { + image, + name, + registry, + organization, + }; + + const jsonData = JSON.stringify(pullOpts); + + if (debug || verbose) { + console.log(`DEBUG: Executing fetch API call: POST ${apiUrl}`); + console.log(`DEBUG: Request body: ${jsonData}`); + } + logger.debug(`Executing API request: POST ${apiUrl}`); + + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 60000); // 60 second timeout for pulls + + const response = await fetch(apiUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: jsonData, + signal: controller.signal, + }); + + clearTimeout(timeoutId); + + if (!response.ok) { + const errorMsg = `HTTP error returned from API server (status: ${response.status})`; + throw new Error(`API request failed: ${errorMsg}`); + } + + const data = (await response.json()) as VMInfo; + + if (debug || verbose) { + console.log(`DEBUG: API response: ${JSON.stringify(data, null, 2)}`); + } + + return data; + } catch (error: any) { + let errorMsg = "Unknown error"; + + if (error.name === "AbortError") { + errorMsg = "Operation timeout - the pull is taking too long"; + } else if (error.code === "ECONNREFUSED") { + errorMsg = "Failed to connect to the API server"; + } else if (error.message) { + errorMsg = error.message; + } + + logger.error(`API request failed: ${errorMsg}`); + throw new Error(`API request failed: ${errorMsg}`); + } +} + +/** + * Delete a VM using fetch. + * + * @param vmName - Name of the VM to delete + * @param host - API host + * @param port - API port + * @param storage - Storage path for the VM + * @param debug - Whether to show debug output + * @param verbose - Enable verbose logging + * @returns Dictionary with API response + */ +export async function lumeApiDelete( + vmName: string, + host: string, + port: number, + storage?: string, + debug: boolean = false, + verbose: boolean = false +): Promise { + // URL encode the storage parameter for the query + let storageParam = ""; + + if (storage) { + const encodedStorage = encodeURIComponent(storage); + storageParam = `?storage=${encodedStorage}`; + } + + // Construct API URL + const apiUrl = `http://${host}:${port}/lume/vms/${vmName}${storageParam}`; + + if (debug || verbose) { + console.log(`DEBUG: Executing fetch API call: DELETE ${apiUrl}`); + } + logger.debug(`Executing API request: DELETE ${apiUrl}`); + + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 20000); + + const response = await fetch(apiUrl, { + method: "DELETE", + signal: controller.signal, + }); + + clearTimeout(timeoutId); + + if (!response.ok && response.status !== 404) { + const errorMsg = `HTTP error returned from API server (status: ${response.status})`; + throw new Error(`API request failed: ${errorMsg}`); + } + + // For 404, return null (VM already deleted) + if (response.status === 404) { + if (debug || verbose) { + console.log("DEBUG: VM not found (404) - treating as already deleted"); + } + return null; + } + + // Try to parse JSON response, but handle empty responses + let data: VMInfo | null = null; + const contentType = response.headers.get("content-type"); + if (contentType && contentType.includes("application/json")) { + try { + data = (await response.json()) as VMInfo; + } catch (e) { + // Empty response is OK for DELETE + } + } else { + // No JSON response expected + } + + if (debug || verbose) { + console.log(`DEBUG: API response: ${JSON.stringify(data, null, 2)}`); + } + + return data; + } catch (error: any) { + let errorMsg = "Unknown error"; + + if (error.name === "AbortError") { + errorMsg = + "Operation timeout - the API server is taking too long to respond"; + } else if (error.code === "ECONNREFUSED") { + errorMsg = "Failed to connect to the API server"; + } else if (error.message) { + errorMsg = error.message; + } + + logger.error(`API request failed: ${errorMsg}`); + throw new Error(`API request failed: ${errorMsg}`); + } +} diff --git a/libs/computer/typescript/src/utils.ts b/libs/computer/typescript/src/utils.ts deleted file mode 100644 index fe1d398f..00000000 --- a/libs/computer/typescript/src/utils.ts +++ /dev/null @@ -1,118 +0,0 @@ -/** - * Utility functions for the Computer module. - */ - -/** - * Sleep for a specified number of milliseconds - * @param ms Number of milliseconds to sleep - */ -export function sleep(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -/** - * Parse a display string in format "WIDTHxHEIGHT" - * @param display Display string to parse - * @returns Object with width and height - */ -export function parseDisplayString(display: string): { - width: number; - height: number; -} { - const match = display.match(/^(\d+)x(\d+)$/); - if (!match || !match[1] || !match[2]) { - throw new Error( - "Display string must be in format 'WIDTHxHEIGHT' (e.g. '1024x768')" - ); - } - return { - width: parseInt(match[1], 10), - height: parseInt(match[2], 10), - }; -} - -/** - * Validate image format (should be in format "image:tag") - * @param image Image string to validate - * @returns Object with image name and tag - */ -export function parseImageString(image: string): { name: string; tag: string } { - const parts = image.split(":"); - if (parts.length !== 2 || !parts[0] || !parts[1]) { - throw new Error("Image must be in the format :"); - } - return { - name: parts[0], - tag: parts[1], - }; -} - -/** - * Convert bytes to human-readable format - * @param bytes Number of bytes - * @returns Human-readable string - */ -export function formatBytes(bytes: number): string { - const units = ["B", "KB", "MB", "GB", "TB"]; - let size = bytes; - let unitIndex = 0; - - while (size >= 1024 && unitIndex < units.length - 1) { - size /= 1024; - unitIndex++; - } - - return `${size.toFixed(2)} ${units[unitIndex]}`; -} - -/** - * Parse memory string (e.g., "8GB") to bytes - * @param memory Memory string - * @returns Number of bytes - */ -export function parseMemoryString(memory: string): number { - const match = memory.match(/^(\d+)(B|KB|MB|GB|TB)?$/i); - if (!match || !match[1] || !match[2]) { - throw new Error("Invalid memory format. Use format like '8GB' or '1024MB'"); - } - - const value = parseInt(match[1], 10); - const unit = match[2].toUpperCase(); - - const multipliers: Record = { - B: 1, - KB: 1024, - MB: 1024 * 1024, - GB: 1024 * 1024 * 1024, - TB: 1024 * 1024 * 1024 * 1024, - }; - - return value * (multipliers[unit] || 1); -} - -/** - * Create a timeout promise that rejects after specified milliseconds - * @param ms Timeout in milliseconds - * @param message Optional error message - */ -export function timeout(ms: number, message?: string): Promise { - return new Promise((_, reject) => { - setTimeout(() => { - reject(new Error(message || `Timeout after ${ms}ms`)); - }, ms); - }); -} - -/** - * Race a promise against a timeout - * @param promise The promise to race - * @param ms Timeout in milliseconds - * @param message Optional timeout error message - */ -export async function withTimeout( - promise: Promise, - ms: number, - message?: string -): Promise { - return Promise.race([promise, timeout(ms, message)]); -} diff --git a/libs/computer/typescript/tests/index.test.ts b/libs/computer/typescript/tests/index.test.ts new file mode 100644 index 00000000..36e38c87 --- /dev/null +++ b/libs/computer/typescript/tests/index.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, it } from "vitest"; +import { Computer, OSType, VMProviderType } from "../src/index"; + +describe("Create Computer Instances", () => { + it("should create a cloud computer", () => { + const computer = Computer.create({ + vmProvider: VMProviderType.CLOUD, + name: "computer-name", + size: "small", + osType: OSType.LINUX, + apiKey: "asdf", + }); + }); + it("should create a lume computer", () => { + const computer = Computer.create({ + vmProvider: VMProviderType.LUME, + display: { width: 1000, height: 1000, scale_factor: 1 }, + image: "computer-image", + memory: "5GB", + cpu: 2, + name: "computer-name", + osType: OSType.MACOS, + }); + }); + it("should create a lumier computer", () => { + const computer = Computer.create({ + vmProvider: VMProviderType.LUMIER, + display: { width: 1000, height: 1000, scale_factor: 1 }, + image: "computer-image", + memory: "5GB", + cpu: 2, + name: "computer-name", + osType: OSType.MACOS, + }); + }); +}); diff --git a/libs/computer/typescript/tests/lume_api.test.ts b/libs/computer/typescript/tests/lume_api.test.ts new file mode 100644 index 00000000..43d4ddbb --- /dev/null +++ b/libs/computer/typescript/tests/lume_api.test.ts @@ -0,0 +1,625 @@ +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; +import { + lumeApiGet, + lumeApiRun, + lumeApiStop, + lumeApiUpdate, + lumeApiPull, + lumeApiDelete, + type VMInfo, + type RunOptions, + type UpdateOptions, +} from "../src/util/lume"; + +const PORT = 1213; +const HOST = "localhost"; + +describe("Lume API", () => { + let lumeServer: any; + + beforeAll(() => { + // Spawn the lume serve process before tests + const { spawn } = require("child_process"); + lumeServer = spawn("lume", ["serve", "--port", PORT], { + stdio: "pipe", + detached: true, + }); + + // Clean up the server when tests are done + afterAll(() => { + if (lumeServer && !lumeServer.killed) { + process.kill(-lumeServer.pid); + } + }); + }); + + describe("lumeApiGet", () => { + it("should fetch VM information successfully", async () => { + // Mock fetch for this test - API returns a single VMDetails object + const mockVMInfo: VMInfo = { + name: "test-vm", + status: "stopped", + diskSize: { allocated: 1024, total: 10240 }, + memorySize: 2048, + os: "ubuntu", + display: "1920x1080", + locationName: "local", + cpuCount: 2, + sharedDirectories: [ + { + hostPath: "/home/user/shared", + tag: "shared", + readOnly: false, + }, + ], + }; + + global.fetch = vi.fn().mockResolvedValueOnce({ + ok: true, + json: async () => mockVMInfo, + headers: new Headers({ "content-type": "application/json" }), + } as Response); + + const result = await lumeApiGet("test-vm", HOST, PORT); + + expect(result).toEqual([mockVMInfo]); + expect(fetch).toHaveBeenCalledWith( + `http://${HOST}:${PORT}/lume/vms/test-vm`, + expect.objectContaining({ + method: "GET", + signal: expect.any(AbortSignal), + }) + ); + }); + + it("should list all VMs when name is empty", async () => { + // Mock fetch for list VMs - API returns an array + const mockVMList: VMInfo[] = [ + { + name: "vm1", + status: "running", + diskSize: { allocated: 1024, total: 10240 }, + memorySize: 2048, + os: "ubuntu", + display: "1920x1080", + locationName: "local", + cpuCount: 2, + }, + { + name: "vm2", + status: "stopped", + diskSize: { allocated: 2048, total: 10240 }, + memorySize: 4096, + os: "debian", + display: "1920x1080", + locationName: "local", + cpuCount: 4, + }, + ]; + + global.fetch = vi.fn().mockResolvedValueOnce({ + ok: true, + json: async () => mockVMList, + headers: new Headers({ "content-type": "application/json" }), + } as Response); + + const result = await lumeApiGet("", HOST, PORT); + + expect(result).toEqual(mockVMList); + expect(fetch).toHaveBeenCalledWith( + `http://${HOST}:${PORT}/lume/vms`, + expect.objectContaining({ + method: "GET", + signal: expect.any(AbortSignal), + }) + ); + }); + + it("should handle storage parameter encoding correctly", async () => { + const mockVMInfo: VMInfo = { + name: "test-vm", + status: "stopped", + diskSize: { allocated: 1024, total: 10240 }, + memorySize: 2048, + os: "ubuntu", + display: "1920x1080", + locationName: "local", + cpuCount: 2, + }; + + global.fetch = vi.fn().mockResolvedValueOnce({ + ok: true, + json: async () => mockVMInfo, + headers: new Headers({ "content-type": "application/json" }), + } as Response); + + const storage = "/path/with spaces/and#special?chars"; + await lumeApiGet("test-vm", HOST, PORT, storage); + + const expectedUrl = `http://${HOST}:${PORT}/lume/vms/test-vm?storage=${encodeURIComponent( + storage + )}`; + expect(fetch).toHaveBeenCalledWith( + expectedUrl, + expect.objectContaining({ + method: "GET", + }) + ); + }); + + it("should handle HTTP errors", async () => { + global.fetch = vi.fn().mockResolvedValueOnce({ + ok: false, + status: 404, + headers: new Headers(), + } as Response); + + await expect(lumeApiGet("test-vm", HOST, PORT)).rejects.toThrow( + "API request failed: HTTP error returned from API server (status: 404)" + ); + }); + + it("should handle connection refused errors", async () => { + const error = new Error("Connection refused"); + (error as any).code = "ECONNREFUSED"; + global.fetch = vi.fn().mockRejectedValueOnce(error); + + await expect(lumeApiGet("test-vm", HOST, PORT)).rejects.toThrow( + "API request failed: Failed to connect to the API server - it might still be starting up" + ); + }); + + it("should handle timeout errors", async () => { + const error = new Error("Request aborted"); + error.name = "AbortError"; + global.fetch = vi.fn().mockRejectedValueOnce(error); + + await expect(lumeApiGet("test-vm", HOST, PORT)).rejects.toThrow( + "API request failed: Operation timeout - the API server is taking too long to respond" + ); + }); + + it("should handle host not found errors", async () => { + const error = new Error("Host not found"); + (error as any).code = "ENOTFOUND"; + global.fetch = vi.fn().mockRejectedValueOnce(error); + + await expect(lumeApiGet("test-vm", HOST, PORT)).rejects.toThrow( + "API request failed: Failed to resolve host - check the API server address" + ); + }); + }); + + describe("lumeApiRun", () => { + it("should run a VM successfully", async () => { + const mockVMInfo: VMInfo = { + name: "test-vm", + status: "running", + diskSize: { allocated: 1024, total: 10240 }, + memorySize: 2048, + os: "ubuntu", + display: "1920x1080", + locationName: "local", + cpuCount: 2, + vncUrl: "vnc://localhost:5900", + ipAddress: "192.168.1.100", + }; + + global.fetch = vi.fn().mockResolvedValueOnce({ + ok: true, + json: async () => mockVMInfo, + headers: new Headers({ "content-type": "application/json" }), + } as Response); + + const runOpts: RunOptions = { + memory: "2G", + cpus: 2, + display: "1920x1080", + }; + + const result = await lumeApiRun("test-vm", HOST, PORT, runOpts); + + expect(result).toEqual(mockVMInfo); + expect(fetch).toHaveBeenCalledWith( + `http://${HOST}:${PORT}/lume/vms/test-vm/run`, + expect.objectContaining({ + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(runOpts), + signal: expect.any(AbortSignal), + }) + ); + }); + + it("should handle storage parameter in run request", async () => { + global.fetch = vi.fn().mockResolvedValueOnce({ + ok: true, + json: async () => ({ name: "test-vm" }), + headers: new Headers({ "content-type": "application/json" }), + } as Response); + + const storage = "/custom/storage/path"; + const runOpts: RunOptions = { memory: "1G" }; + + await lumeApiRun("test-vm", HOST, PORT, runOpts, storage); + + const expectedUrl = `http://${HOST}:${PORT}/lume/vms/test-vm/run?storage=${encodeURIComponent( + storage + )}`; + expect(fetch).toHaveBeenCalledWith( + expectedUrl, + expect.objectContaining({ + method: "POST", + }) + ); + }); + + it("should handle run errors", async () => { + global.fetch = vi.fn().mockResolvedValueOnce({ + ok: false, + status: 500, + headers: new Headers(), + } as Response); + + await expect( + lumeApiRun("test-vm", HOST, PORT, {}) + ).rejects.toThrow( + "API request failed: HTTP error returned from API server (status: 500)" + ); + }); + }); + + describe("lumeApiStop", () => { + it("should stop a VM successfully", async () => { + const mockVMInfo: VMInfo = { + name: "test-vm", + status: "stopped", + diskSize: { allocated: 1024, total: 10240 }, + memorySize: 2048, + os: "ubuntu", + display: "1920x1080", + locationName: "local", + cpuCount: 2, + }; + + global.fetch = vi.fn().mockResolvedValueOnce({ + ok: true, + json: async () => mockVMInfo, + headers: new Headers({ "content-type": "application/json" }), + } as Response); + + const result = await lumeApiStop("test-vm", HOST, PORT); + + expect(result).toEqual(mockVMInfo); + expect(fetch).toHaveBeenCalledWith( + `http://${HOST}:${PORT}/lume/vms/test-vm/stop`, + expect.objectContaining({ + method: "POST", + signal: expect.any(AbortSignal), + headers: { + "Content-Type": "application/json", + }, + body: "{}", + }) + ); + }); + + it("should handle storage parameter in stop request", async () => { + global.fetch = vi.fn().mockResolvedValueOnce({ + ok: true, + json: async () => ({ name: "test-vm" }), + headers: new Headers({ "content-type": "application/json" }), + } as Response); + + const storage = "/storage/path"; + await lumeApiStop("test-vm", HOST, PORT, storage); + + const expectedUrl = `http://${HOST}:${PORT}/lume/vms/test-vm/stop?storage=${encodeURIComponent( + storage + )}`; + expect(fetch).toHaveBeenCalledWith( + expectedUrl, + expect.objectContaining({ + method: "POST", + }) + ); + }); + }); + + describe("lumeApiUpdate", () => { + it("should update VM settings successfully", async () => { + const mockVMInfo: VMInfo = { + name: "test-vm", + status: "stopped", + diskSize: { allocated: 1024, total: 10240 }, + memorySize: 4096, + os: "ubuntu", + display: "2560x1440", + locationName: "local", + cpuCount: 2, + }; + + global.fetch = vi.fn().mockResolvedValueOnce({ + ok: true, + json: async () => mockVMInfo, + headers: new Headers({ "content-type": "application/json" }), + } as Response); + + const updateOpts: UpdateOptions = { + memory: "4G", + display: "2560x1440", + }; + + const result = await lumeApiUpdate("test-vm", HOST, PORT, updateOpts); + + expect(result).toEqual(mockVMInfo); + expect(fetch).toHaveBeenCalledWith( + `http://${HOST}:${PORT}/lume/vms/test-vm/update`, + expect.objectContaining({ + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(updateOpts), + signal: expect.any(AbortSignal), + }) + ); + }); + + it("should handle empty update options", async () => { + global.fetch = vi.fn().mockResolvedValueOnce({ + ok: true, + json: async () => ({ name: "test-vm" }), + headers: new Headers({ "content-type": "application/json" }), + } as Response); + + await lumeApiUpdate("test-vm", HOST, PORT, {}); + + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + body: "{}", + }) + ); + }); + }); + + describe("lumeApiPull", () => { + it("should pull a VM image successfully", async () => { + const mockVMInfo: VMInfo = { + name: "pulled-vm", + status: "stopped", + diskSize: { allocated: 2048, total: 10240 }, + memorySize: 2048, + os: "ubuntu", + display: "1920x1080", + locationName: "local", + cpuCount: 2, + }; + + global.fetch = vi.fn().mockResolvedValueOnce({ + ok: true, + json: async () => mockVMInfo, + headers: new Headers({ "content-type": "application/json" }), + } as Response); + + const result = await lumeApiPull( + "ubuntu:latest", + "pulled-vm", + HOST, + PORT + ); + + expect(result).toEqual(mockVMInfo); + expect(fetch).toHaveBeenCalledWith( + `http://${HOST}:${PORT}/lume/pull`, + expect.objectContaining({ + method: "POST", + signal: expect.any(AbortSignal), + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + image: "ubuntu:latest", + name: "pulled-vm", + registry: "ghcr.io", + organization: "trycua", + }), + }) + ); + }); + + it("should use custom registry and organization", async () => { + global.fetch = vi.fn().mockResolvedValueOnce({ + ok: true, + json: async () => ({ name: "custom-vm" }), + headers: new Headers({ "content-type": "application/json" }), + } as Response); + + await lumeApiPull( + "custom:tag", + "custom-vm", + HOST, + PORT, + undefined, + "docker.io", + "myorg" + ); + + expect(fetch).toHaveBeenCalledWith( + expect.any(String), + expect.objectContaining({ + body: JSON.stringify({ + image: "custom:tag", + name: "custom-vm", + registry: "docker.io", + organization: "myorg", + }), + }) + ); + }); + + it("should handle storage parameter in pull request", async () => { + global.fetch = vi.fn().mockResolvedValueOnce({ + ok: true, + json: async () => ({ name: "test-vm" }), + headers: new Headers({ "content-type": "application/json" }), + } as Response); + + const storage = "/custom/storage"; + await lumeApiPull("image:tag", "test-vm", HOST, PORT, storage); + + const expectedUrl = `http://${HOST}:${PORT}/lume/pull?storage=${encodeURIComponent( + storage + )}`; + expect(fetch).toHaveBeenCalledWith( + expectedUrl, + expect.objectContaining({ + method: "POST", + }) + ); + }); + }); + + describe("lumeApiDelete", () => { + it("should delete a VM successfully", async () => { + const mockVMInfo: VMInfo = { + name: "test-vm", + status: "deleted", + diskSize: { allocated: 0, total: 0 }, + memorySize: 0, + os: "ubuntu", + display: "", + locationName: "local", + cpuCount: 0, + }; + + global.fetch = vi.fn().mockResolvedValueOnce({ + ok: true, + json: async () => mockVMInfo, + headers: new Headers({ "content-type": "application/json" }), + } as Response); + + const result = await lumeApiDelete("test-vm", HOST, PORT); + + expect(result).toEqual(mockVMInfo); + expect(fetch).toHaveBeenCalledWith( + `http://${HOST}:${PORT}/lume/vms/test-vm`, + expect.objectContaining({ + method: "DELETE", + signal: expect.any(AbortSignal), + }) + ); + }); + + it("should handle storage parameter in delete request", async () => { + const storage = "/custom/storage"; + global.fetch = vi.fn().mockResolvedValueOnce({ + ok: true, + json: async () => null, + headers: new Headers(), + } as Response); + + await lumeApiDelete("test-vm", HOST, PORT, storage); + + const expectedUrl = `http://${HOST}:${PORT}/lume/vms/test-vm?storage=${encodeURIComponent( + storage + )}`; + expect(fetch).toHaveBeenCalledWith( + expectedUrl, + expect.objectContaining({ + method: "DELETE", + }) + ); + }); + + it("should handle 404 as successful deletion", async () => { + global.fetch = vi.fn().mockResolvedValueOnce({ + ok: false, + status: 404, + headers: new Headers(), + } as Response); + + const result = await lumeApiDelete("non-existent-vm", HOST, PORT); + + expect(result).toBeNull(); + }); + + it("should throw error for non-404 HTTP errors", async () => { + global.fetch = vi.fn().mockResolvedValueOnce({ + ok: false, + status: 500, + headers: new Headers(), + } as Response); + + await expect(lumeApiDelete("test-vm", HOST, PORT)).rejects.toThrow( + "API request failed: HTTP error returned from API server (status: 500)" + ); + }); + }); + + describe("Debug and Verbose Logging", () => { + it("should log debug information when debug is true", async () => { + const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + + global.fetch = vi.fn().mockResolvedValueOnce({ + ok: true, + json: async () => [{ name: "test-vm", cpuCount: 2 }], + headers: new Headers({ "content-type": "application/json" }), + } as Response); + + await lumeApiGet("", HOST, PORT, undefined, true); // Empty name for list + + expect(consoleSpy).toHaveBeenCalledWith( + expect.stringContaining("DEBUG: API response:") + ); + + consoleSpy.mockRestore(); + }); + + it("should log verbose information when verbose is true", async () => { + const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {}); + + global.fetch = vi.fn().mockResolvedValueOnce({ + ok: true, + json: async () => ({ name: "test-vm", cpuCount: 2 }), + headers: new Headers({ "content-type": "application/json" }), + } as Response); + + await lumeApiRun( + "test-vm", + HOST, + PORT, + { memory: "1G" }, + undefined, + false, + true + ); + + expect(consoleSpy).toHaveBeenCalled(); + + consoleSpy.mockRestore(); + }); + }); + + describe("Error Message Handling", () => { + it("should handle generic errors with message", async () => { + const error = new Error("Custom error message"); + global.fetch = vi.fn().mockRejectedValueOnce(error); + + await expect(lumeApiGet("test-vm", HOST, PORT)).rejects.toThrow( + "API request failed: Custom error message" + ); + }); + + it("should handle unknown errors", async () => { + global.fetch = vi.fn().mockRejectedValueOnce({}); + + await expect(lumeApiGet("test-vm", HOST, PORT)).rejects.toThrow( + "API request failed: Unknown error" + ); + }); + }); +}); From f9f88a5f88294ecb5656f5b8e5e73d7940308ce6 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Tue, 17 Jun 2025 13:51:26 -0400 Subject: [PATCH 055/141] Add tests for lume provider -- warn: may not be actually useful --- .../typescript/tests/computer/lume.test.ts | 484 ++++++++++++++++++ 1 file changed, 484 insertions(+) create mode 100644 libs/computer/typescript/tests/computer/lume.test.ts diff --git a/libs/computer/typescript/tests/computer/lume.test.ts b/libs/computer/typescript/tests/computer/lume.test.ts new file mode 100644 index 00000000..83d97af3 --- /dev/null +++ b/libs/computer/typescript/tests/computer/lume.test.ts @@ -0,0 +1,484 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { LumeComputer } from "../../src/computer/providers/lume"; +import { VMProviderType, OSType } from "../../src/computer/types"; +import type { LumeComputerConfig, Display } from "../../src/computer/types"; +import type { VMInfo } from "../../src/util/lume"; +import * as lumeApi from "../../src/util/lume"; + +// Mock the lume API module +vi.mock("../../src/util/lume", () => ({ + lumeApiGet: vi.fn(), + lumeApiRun: vi.fn(), + lumeApiStop: vi.fn(), + lumeApiUpdate: vi.fn(), + lumeApiPull: vi.fn(), + lumeApiDelete: vi.fn(), +})); + +// Mock pino logger +vi.mock("pino", () => ({ + default: () => ({ + info: vi.fn(), + error: vi.fn(), + debug: vi.fn(), + warn: vi.fn(), + }), +})); + +describe("LumeComputer", () => { + const mockVMInfo: VMInfo = { + name: "test-vm", + status: "running", + diskSize: { allocated: 1024, total: 10240 }, + memorySize: 2048, + os: "macos", + display: "1920x1080", + locationName: "local", + cpuCount: 4, + ipAddress: "192.168.1.100", + vncUrl: "vnc://localhost:5900", + sharedDirectories: [], + }; + + const defaultConfig: LumeComputerConfig = { + name: "test-vm", + osType: OSType.MACOS, + vmProvider: VMProviderType.LUME, + display: "1920x1080", + memory: "8GB", + cpu: 4, + image: "macos-sequoia-cua:latest", + port: 7777, + host: "localhost", + ephemeral: false, + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe("constructor", () => { + it("should initialize with default config", () => { + const computer = new LumeComputer(defaultConfig); + expect(computer.getName()).toBe("test-vm"); + expect(computer.getOSType()).toBe(OSType.MACOS); + expect(computer.getVMProviderType()).toBe(VMProviderType.LUME); + }); + + it("should accept display as string", () => { + const config = { ...defaultConfig, display: "1024x768" }; + const computer = new LumeComputer(config); + expect(computer).toBeDefined(); + }); + + it("should accept display as Display object", () => { + const display: Display = { width: 1920, height: 1080, scale_factor: 2 }; + const config = { ...defaultConfig, display }; + const computer = new LumeComputer(config); + expect(computer).toBeDefined(); + }); + }); + + describe("getVm", () => { + it("should get VM info successfully", async () => { + vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]); + + const computer = new LumeComputer(defaultConfig); + const result = await computer.getVm("test-vm"); + + expect(lumeApi.lumeApiGet).toHaveBeenCalledWith("test-vm", "localhost", 7777, undefined); + expect(result).toEqual(mockVMInfo); + }); + + it("should handle VM not found error", async () => { + vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([]); + + const computer = new LumeComputer(defaultConfig); + await expect(computer.getVm("test-vm")).rejects.toThrow("VM Not Found."); + }); + + it("should handle stopped VM state", async () => { + const stoppedVM = { ...mockVMInfo, status: "stopped", ipAddress: undefined }; + vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([stoppedVM]); + + const computer = new LumeComputer(defaultConfig); + const result = await computer.getVm("test-vm"); + + expect(result.status).toBe("stopped"); + expect(result.name).toBe("test-vm"); + }); + + it("should handle VM without IP address", async () => { + const noIpVM = { ...mockVMInfo, ipAddress: undefined }; + vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([noIpVM]); + + const computer = new LumeComputer(defaultConfig); + const result = await computer.getVm("test-vm"); + + expect(result).toEqual(noIpVM); + }); + + it("should pass storage parameter", async () => { + vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]); + + const computer = new LumeComputer(defaultConfig); + await computer.getVm("test-vm", "/custom/storage"); + + expect(lumeApi.lumeApiGet).toHaveBeenCalledWith("test-vm", "localhost", 7777, "/custom/storage"); + }); + }); + + describe("listVm", () => { + it("should list all VMs", async () => { + const vmList = [mockVMInfo, { ...mockVMInfo, name: "another-vm" }]; + vi.mocked(lumeApi.lumeApiGet).mockResolvedValue(vmList); + + const computer = new LumeComputer(defaultConfig); + const result = await computer.listVm(); + + expect(lumeApi.lumeApiGet).toHaveBeenCalledWith("", "localhost", 7777); + expect(result).toEqual(vmList); + }); + }); + + describe("runVm", () => { + it("should run VM when it already exists", async () => { + vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]); + vi.mocked(lumeApi.lumeApiRun).mockResolvedValue(mockVMInfo); + + const computer = new LumeComputer(defaultConfig); + const runOpts = { memory: "4GB" }; + const result = await computer.runVm("macos-sequoia-cua:latest", "test-vm", runOpts); + + expect(lumeApi.lumeApiGet).toHaveBeenCalledWith("test-vm", "localhost", 7777, undefined); + expect(lumeApi.lumeApiRun).toHaveBeenCalledWith("test-vm", "localhost", 7777, runOpts, undefined); + expect(result).toEqual(mockVMInfo); + }); + + it("should pull and run VM when it doesn't exist", async () => { + vi.mocked(lumeApi.lumeApiGet).mockRejectedValueOnce(new Error("VM not found")); + vi.mocked(lumeApi.lumeApiPull).mockResolvedValue(mockVMInfo); + vi.mocked(lumeApi.lumeApiRun).mockResolvedValue(mockVMInfo); + + const computer = new LumeComputer(defaultConfig); + const result = await computer.runVm("macos-sequoia-cua:latest", "test-vm"); + + expect(lumeApi.lumeApiGet).toHaveBeenCalled(); + expect(lumeApi.lumeApiPull).toHaveBeenCalledWith( + "macos-sequoia-cua:latest", + "test-vm", + "localhost", + 7777, + undefined, + "ghcr.io", + "trycua" + ); + expect(lumeApi.lumeApiRun).toHaveBeenCalled(); + expect(result).toEqual(mockVMInfo); + }); + + it("should handle pull failure", async () => { + vi.mocked(lumeApi.lumeApiGet).mockRejectedValueOnce(new Error("VM not found")); + vi.mocked(lumeApi.lumeApiPull).mockRejectedValue(new Error("Pull failed")); + + const computer = new LumeComputer(defaultConfig); + await expect(computer.runVm("macos-sequoia-cua:latest", "test-vm")).rejects.toThrow("Pull failed"); + }); + + it("should pass storage parameter", async () => { + vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]); + vi.mocked(lumeApi.lumeApiRun).mockResolvedValue(mockVMInfo); + + const computer = new LumeComputer(defaultConfig); + await computer.runVm("macos-sequoia-cua:latest", "test-vm", {}, "/storage"); + + expect(lumeApi.lumeApiGet).toHaveBeenCalledWith("test-vm", "localhost", 7777, "/storage"); + expect(lumeApi.lumeApiRun).toHaveBeenCalledWith("test-vm", "localhost", 7777, {}, "/storage"); + }); + }); + + describe("stopVm", () => { + it("should stop VM normally", async () => { + vi.mocked(lumeApi.lumeApiStop).mockResolvedValue(mockVMInfo); + + const computer = new LumeComputer(defaultConfig); + const result = await computer.stopVm("test-vm"); + + expect(lumeApi.lumeApiStop).toHaveBeenCalledWith("test-vm", "localhost", 7777, undefined); + expect(result).toEqual(mockVMInfo); + }); + + it("should delete VM after stopping in ephemeral mode", async () => { + const stoppedVM = { ...mockVMInfo, status: "stopped" }; + vi.mocked(lumeApi.lumeApiStop).mockResolvedValue(stoppedVM); + vi.mocked(lumeApi.lumeApiDelete).mockResolvedValue(null); + + const ephemeralConfig = { ...defaultConfig, ephemeral: true }; + const computer = new LumeComputer(ephemeralConfig); + const result = await computer.stopVm("test-vm"); + + expect(lumeApi.lumeApiStop).toHaveBeenCalled(); + expect(lumeApi.lumeApiDelete).toHaveBeenCalledWith("test-vm", "localhost", 7777, undefined, false, false); + expect(result).toMatchObject({ + ...stoppedVM, + deleted: true, + deleteResult: null, + }); + }); + + it("should handle delete failure in ephemeral mode", async () => { + vi.mocked(lumeApi.lumeApiStop).mockResolvedValue(mockVMInfo); + vi.mocked(lumeApi.lumeApiDelete).mockRejectedValue(new Error("Delete failed")); + + const ephemeralConfig = { ...defaultConfig, ephemeral: true }; + const computer = new LumeComputer(ephemeralConfig); + + await expect(computer.stopVm("test-vm")).rejects.toThrow("Failed to delete ephemeral VM test-vm: Error: Failed to delete VM: Error: Delete failed"); + }); + + it("should not delete VM if stop returns error in ephemeral mode", async () => { + const errorResponse = { error: "Stop failed" } as any; + vi.mocked(lumeApi.lumeApiStop).mockResolvedValue(errorResponse); + + const ephemeralConfig = { ...defaultConfig, ephemeral: true }; + const computer = new LumeComputer(ephemeralConfig); + const result = await computer.stopVm("test-vm"); + + expect(lumeApi.lumeApiDelete).not.toHaveBeenCalled(); + expect(result).toEqual(errorResponse); + }); + }); + + describe("pullVm", () => { + it("should pull VM image successfully", async () => { + vi.mocked(lumeApi.lumeApiPull).mockResolvedValue(mockVMInfo); + + const computer = new LumeComputer(defaultConfig); + const result = await computer.pullVm("test-vm", "ubuntu:latest"); + + expect(lumeApi.lumeApiPull).toHaveBeenCalledWith( + "ubuntu:latest", + "test-vm", + "localhost", + 7777, + undefined, + "ghcr.io", + "trycua" + ); + expect(result).toEqual(mockVMInfo); + }); + + it("should throw error if image parameter is missing", async () => { + const computer = new LumeComputer(defaultConfig); + await expect(computer.pullVm("test-vm", "")).rejects.toThrow("Image parameter is required for pullVm"); + }); + + it("should use custom registry and organization", async () => { + vi.mocked(lumeApi.lumeApiPull).mockResolvedValue(mockVMInfo); + + const computer = new LumeComputer(defaultConfig); + await computer.pullVm("test-vm", "custom:tag", "/storage", "docker.io", "myorg"); + + expect(lumeApi.lumeApiPull).toHaveBeenCalledWith( + "custom:tag", + "test-vm", + "localhost", + 7777, + "/storage", + "docker.io", + "myorg" + ); + }); + + it("should handle pull failure", async () => { + vi.mocked(lumeApi.lumeApiPull).mockRejectedValue(new Error("Network error")); + + const computer = new LumeComputer(defaultConfig); + await expect(computer.pullVm("test-vm", "ubuntu:latest")).rejects.toThrow("Failed to pull VM: Error: Network error"); + }); + }); + + describe("deleteVm", () => { + it("should delete VM successfully", async () => { + vi.mocked(lumeApi.lumeApiDelete).mockResolvedValue(null); + + const computer = new LumeComputer(defaultConfig); + const result = await computer.deleteVm("test-vm"); + + expect(lumeApi.lumeApiDelete).toHaveBeenCalledWith("test-vm", "localhost", 7777, undefined, false, false); + expect(result).toBeNull(); + }); + + it("should handle delete failure", async () => { + vi.mocked(lumeApi.lumeApiDelete).mockRejectedValue(new Error("Permission denied")); + + const computer = new LumeComputer(defaultConfig); + await expect(computer.deleteVm("test-vm")).rejects.toThrow("Failed to delete VM: Error: Permission denied"); + }); + + it("should pass storage parameter", async () => { + vi.mocked(lumeApi.lumeApiDelete).mockResolvedValue(null); + + const computer = new LumeComputer(defaultConfig); + await computer.deleteVm("test-vm", "/storage"); + + expect(lumeApi.lumeApiDelete).toHaveBeenCalledWith("test-vm", "localhost", 7777, "/storage", false, false); + }); + }); + + describe("updateVm", () => { + it("should update VM configuration", async () => { + const updatedVM = { ...mockVMInfo, memorySize: 4096 }; + vi.mocked(lumeApi.lumeApiUpdate).mockResolvedValue(updatedVM); + + const computer = new LumeComputer(defaultConfig); + const updateOpts = { memory: "4GB" }; + const result = await computer.updateVm("test-vm", updateOpts); + + expect(lumeApi.lumeApiUpdate).toHaveBeenCalledWith( + "test-vm", + "localhost", + 7777, + updateOpts, + undefined, + false, + false + ); + expect(result).toEqual(updatedVM); + }); + + it("should pass storage parameter", async () => { + vi.mocked(lumeApi.lumeApiUpdate).mockResolvedValue(mockVMInfo); + + const computer = new LumeComputer(defaultConfig); + await computer.updateVm("test-vm", {}, "/storage"); + + expect(lumeApi.lumeApiUpdate).toHaveBeenCalledWith( + "test-vm", + "localhost", + 7777, + {}, + "/storage", + false, + false + ); + }); + }); + + describe("getIp", () => { + it("should return IP address immediately if available", async () => { + vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]); + + const computer = new LumeComputer(defaultConfig); + const ip = await computer.getIp("test-vm"); + + expect(ip).toBe("192.168.1.100"); + expect(lumeApi.lumeApiGet).toHaveBeenCalledTimes(1); + }); + + it("should retry until IP address is available", async () => { + const noIpVM = { ...mockVMInfo, ipAddress: undefined }; + vi.mocked(lumeApi.lumeApiGet) + .mockResolvedValueOnce([noIpVM]) + .mockResolvedValueOnce([noIpVM]) + .mockResolvedValueOnce([mockVMInfo]); + + const computer = new LumeComputer(defaultConfig); + const ip = await computer.getIp("test-vm", undefined, 0.1); // Short retry delay for testing + + expect(ip).toBe("192.168.1.100"); + expect(lumeApi.lumeApiGet).toHaveBeenCalledTimes(3); + }); + + it("should throw error if VM is stopped", async () => { + const stoppedVM = { ...mockVMInfo, status: "stopped", ipAddress: undefined }; + vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([stoppedVM]); + + const computer = new LumeComputer(defaultConfig); + await expect(computer.getIp("test-vm")).rejects.toThrow( + "VM test-vm is in 'stopped' state and will not get an IP address" + ); + }); + + it("should throw error if VM is in error state", async () => { + const errorVM = { ...mockVMInfo, status: "error", ipAddress: undefined }; + vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([errorVM]); + + const computer = new LumeComputer(defaultConfig); + await expect(computer.getIp("test-vm")).rejects.toThrow( + "VM test-vm is in 'error' state and will not get an IP address" + ); + }); + + it("should handle getVm errors", async () => { + vi.mocked(lumeApi.lumeApiGet).mockRejectedValue(new Error("Network error")); + + const computer = new LumeComputer(defaultConfig); + await expect(computer.getIp("test-vm")).rejects.toThrow("Network error"); + }); + + it("should pass storage parameter", async () => { + vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]); + + const computer = new LumeComputer(defaultConfig); + await computer.getIp("test-vm", "/storage"); + + expect(lumeApi.lumeApiGet).toHaveBeenCalledWith("test-vm", "localhost", 7777, "/storage"); + }); + }); + + describe("integration scenarios", () => { + it("should handle full VM lifecycle", async () => { + // Simulate VM not existing initially + vi.mocked(lumeApi.lumeApiGet).mockRejectedValueOnce(new Error("VM not found")); + vi.mocked(lumeApi.lumeApiPull).mockResolvedValue(mockVMInfo); + + // Simulate VM starting without IP, then getting IP + const startingVM = { ...mockVMInfo, ipAddress: undefined, status: "starting" }; + vi.mocked(lumeApi.lumeApiRun).mockResolvedValue(startingVM); + vi.mocked(lumeApi.lumeApiGet) + .mockResolvedValueOnce([startingVM]) + .mockResolvedValueOnce([mockVMInfo]); + + // Simulate stop + const stoppedVM = { ...mockVMInfo, status: "stopped" }; + vi.mocked(lumeApi.lumeApiStop).mockResolvedValue(stoppedVM); + + const computer = new LumeComputer(defaultConfig); + + // Run VM (should pull first) + await computer.runVm("macos-sequoia-cua:latest", "test-vm"); + + // Get IP (should retry once) + const ip = await computer.getIp("test-vm", undefined, 0.1); + expect(ip).toBe("192.168.1.100"); + + // Stop VM + const stopResult = await computer.stopVm("test-vm"); + expect(stopResult.status).toBe("stopped"); + }); + + it("should handle ephemeral VM lifecycle", async () => { + const ephemeralConfig = { ...defaultConfig, ephemeral: true }; + const computer = new LumeComputer(ephemeralConfig); + + // Setup mocks + vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]); + vi.mocked(lumeApi.lumeApiRun).mockResolvedValue(mockVMInfo); + vi.mocked(lumeApi.lumeApiStop).mockResolvedValue({ ...mockVMInfo, status: "stopped" }); + vi.mocked(lumeApi.lumeApiDelete).mockResolvedValue(null); + + // Run and stop ephemeral VM + await computer.runVm("macos-sequoia-cua:latest", "test-vm"); + const result = await computer.stopVm("test-vm"); + + // Verify VM was deleted + expect(lumeApi.lumeApiDelete).toHaveBeenCalled(); + expect((result as any).deleted).toBe(true); + }); + }); +}); \ No newline at end of file From 769d51c4d2b420816214096724518a8f1f17fa71 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 17 Jun 2025 15:46:58 -0400 Subject: [PATCH 056/141] Add Claude 4 to agent UI --- libs/agent/agent/ui/gradio/app.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/libs/agent/agent/ui/gradio/app.py b/libs/agent/agent/ui/gradio/app.py index fc328518..84635b73 100644 --- a/libs/agent/agent/ui/gradio/app.py +++ b/libs/agent/agent/ui/gradio/app.py @@ -137,6 +137,7 @@ MODEL_MAPPINGS = { "openai": { # Default to operator CUA model "default": "computer-use-preview", + "OpenAI: Computer-Use Preview": "computer-use-preview", # Map standard OpenAI model names to CUA-specific model names "gpt-4-turbo": "computer-use-preview", "gpt-4o": "computer-use-preview", @@ -147,9 +148,13 @@ MODEL_MAPPINGS = { "anthropic": { # Default to newest model "default": "claude-3-7-sonnet-20250219", + # New Claude 4 models + "Anthropic: Claude 4 Opus (20250514)": "claude-opus-4-20250514", + "Anthropic: Claude 4 Sonnet (20250514)": "claude-sonnet-4-20250514", + # Specific Claude models for CUA - "claude-3-5-sonnet-20240620": "claude-3-5-sonnet-20240620", - "claude-3-7-sonnet-20250219": "claude-3-7-sonnet-20250219", + "Anthropic: Claude 3.7 Sonnet (20250219)": "claude-3-5-sonnet-20240620", + "Anthropic: Claude 3.5 Sonnet (20240620)": "claude-3-7-sonnet-20250219", # Map standard model names to CUA-specific model names "claude-3-opus": "claude-3-7-sonnet-20250219", "claude-3-sonnet": "claude-3-5-sonnet-20240620", @@ -209,12 +214,12 @@ def get_provider_and_model(model_name: str, loop_provider: str) -> tuple: if agent_loop == AgentLoop.OPENAI: provider = LLMProvider.OPENAI model_name_to_use = MODEL_MAPPINGS["openai"].get( - model_name.lower(), MODEL_MAPPINGS["openai"]["default"] + model_name, MODEL_MAPPINGS["openai"]["default"] ) elif agent_loop == AgentLoop.ANTHROPIC: provider = LLMProvider.ANTHROPIC model_name_to_use = MODEL_MAPPINGS["anthropic"].get( - model_name.lower(), MODEL_MAPPINGS["anthropic"]["default"] + model_name, MODEL_MAPPINGS["anthropic"]["default"] ) elif agent_loop == AgentLoop.OMNI: # Determine provider and clean model name based on the full string from UI @@ -309,6 +314,8 @@ def get_provider_and_model(model_name: str, loop_provider: str) -> tuple: model_name_to_use = MODEL_MAPPINGS["openai"]["default"] agent_loop = AgentLoop.OPENAI + print(f"Mapping {model_name} and {loop_provider} to {provider}, {model_name_to_use}, {agent_loop}") + return provider, model_name_to_use, agent_loop @@ -453,6 +460,9 @@ def create_gradio_ui( # Always show models regardless of API key availability openai_models = ["OpenAI: Computer-Use Preview"] anthropic_models = [ + "Anthropic: Claude 4 Opus (20250514)", + "Anthropic: Claude 4 Sonnet (20250514)", + "Anthropic: Claude 3.7 Sonnet (20250219)", "Anthropic: Claude 3.5 Sonnet (20240620)", ] From 11f9e2ecaaff8581025d13a1060520383009cbdb Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 17 Jun 2025 16:12:55 -0400 Subject: [PATCH 057/141] Fix claude omni mappings --- libs/agent/agent/ui/gradio/app.py | 36 +++++++++---------------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/libs/agent/agent/ui/gradio/app.py b/libs/agent/agent/ui/gradio/app.py index 84635b73..0593d776 100644 --- a/libs/agent/agent/ui/gradio/app.py +++ b/libs/agent/agent/ui/gradio/app.py @@ -151,10 +151,14 @@ MODEL_MAPPINGS = { # New Claude 4 models "Anthropic: Claude 4 Opus (20250514)": "claude-opus-4-20250514", "Anthropic: Claude 4 Sonnet (20250514)": "claude-sonnet-4-20250514", + "claude-opus-4-20250514": "claude-opus-4-20250514", + "claude-sonnet-4-20250514": "claude-sonnet-4-20250514", # Specific Claude models for CUA "Anthropic: Claude 3.7 Sonnet (20250219)": "claude-3-5-sonnet-20240620", "Anthropic: Claude 3.5 Sonnet (20240620)": "claude-3-7-sonnet-20250219", + "claude-3-7-sonnet-20250219": "claude-3-7-sonnet-20250219", + "claude-3-5-sonnet-20240620": "claude-3-5-sonnet-20240620", # Map standard model names to CUA-specific model names "claude-3-opus": "claude-3-7-sonnet-20250219", "claude-3-sonnet": "claude-3-5-sonnet-20240620", @@ -239,33 +243,11 @@ def get_provider_and_model(model_name: str, loop_provider: str) -> tuple: cleaned_model_name = model_name.split("OMNI: Ollama ", 1)[1] elif model_name.startswith("OMNI: Claude "): provider = LLMProvider.ANTHROPIC - # Extract the canonical model name based on the UI string - # e.g., "OMNI: Claude 3.7 Sonnet (20250219)" -> "3.7 Sonnet" and "20250219" - parts = model_name.split(" (") - model_key_part = parts[0].replace("OMNI: Claude ", "") - date_part = parts[1].replace(")", "") if len(parts) > 1 else "" - # Normalize the extracted key part for comparison - # "3.7 Sonnet" -> "37sonnet" - model_key_part_norm = model_key_part.lower().replace(".", "").replace(" ", "") - - cleaned_model_name = MODEL_MAPPINGS["omni"]["default"] # Default if not found - # Find the canonical name in the main Anthropic map - for key_anthropic, val_anthropic in MODEL_MAPPINGS["anthropic"].items(): - # Normalize the canonical key for comparison - # "claude-3-7-sonnet-20250219" -> "claude37sonnet20250219" - key_anthropic_norm = key_anthropic.lower().replace("-", "") - - # Check if the normalized canonical key starts with "claude" + normalized extracted part - # AND contains the date part. - if ( - key_anthropic_norm.startswith("claude" + model_key_part_norm) - and date_part in key_anthropic_norm - ): - cleaned_model_name = ( - val_anthropic # Use the canonical name like "claude-3-7-sonnet-20250219" - ) - break + model_name = model_name.replace("OMNI: ", "Anthropic: ") + cleaned_model_name = MODEL_MAPPINGS["anthropic"].get( + model_name, MODEL_MAPPINGS["anthropic"]["default"] + ) elif model_name.startswith("OMNI: OpenAI "): provider = LLMProvider.OPENAI # Extract the model part, e.g., "GPT-4o mini" @@ -470,6 +452,8 @@ def create_gradio_ui( "OMNI: OpenAI GPT-4o", "OMNI: OpenAI GPT-4o mini", "OMNI: OpenAI GPT-4.5-preview", + "OMNI: Claude 4 Opus (20250514)", + "OMNI: Claude 4 Sonnet (20250514)", "OMNI: Claude 3.7 Sonnet (20250219)", "OMNI: Claude 3.5 Sonnet (20240620)" ] From e2f9427b41a3771777158826d7d09ee4e283bc46 Mon Sep 17 00:00:00 2001 From: ddupont <3820588+ddupont808@users.noreply.github.com> Date: Wed, 18 Jun 2025 12:52:49 -0400 Subject: [PATCH 058/141] Update winsandbox_example.py Added pause to example --- examples/winsandbox_example.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/winsandbox_example.py b/examples/winsandbox_example.py index 2f7e3f68..9cf1269a 100644 --- a/examples/winsandbox_example.py +++ b/examples/winsandbox_example.py @@ -33,6 +33,9 @@ async def main(): print("Testing command execution...") stdout, stderr = await computer.interface.run_command("echo Hello from Windows Sandbox!") print(f"Command output: {stdout}") + + print("Press any key to continue...") + input() except Exception as e: print(f"Error: {e}") From affe9d587851997e92ed1e58355a849a25f3b398 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 18 Jun 2025 10:21:36 -0700 Subject: [PATCH 059/141] Add warnings to lume implementation, get set up for cloud implementation of computer --- .../typescript/src/computer/defaults.ts | 2 +- .../computer/typescript/src/computer/index.ts | 5 -- .../src/computer/providers/cloud.ts | 6 +- .../typescript/src/computer/providers/lume.ts | 5 ++ .../src/computer/providers/lumier.ts | 60 ------------------- .../computer/typescript/src/computer/types.ts | 14 +---- libs/computer/typescript/src/util/logger.ts | 7 --- libs/computer/typescript/src/util/lume.ts | 11 +++- libs/computer/typescript/tests/index.test.ts | 24 +------- 9 files changed, 22 insertions(+), 112 deletions(-) delete mode 100644 libs/computer/typescript/src/computer/providers/lumier.ts delete mode 100644 libs/computer/typescript/src/util/logger.ts diff --git a/libs/computer/typescript/src/computer/defaults.ts b/libs/computer/typescript/src/computer/defaults.ts index 26e6d3c4..adf0a5c1 100644 --- a/libs/computer/typescript/src/computer/defaults.ts +++ b/libs/computer/typescript/src/computer/defaults.ts @@ -7,7 +7,7 @@ import type { BaseComputerConfig, Display } from "./types"; export const DEFAULT_CONFIG: Partial = { name: "", osType: OSType.MACOS, - vmProvider: VMProviderType.LUME, + vmProvider: VMProviderType.CLOUD, display: "1024x768", memory: "8GB", cpu: 4, diff --git a/libs/computer/typescript/src/computer/index.ts b/libs/computer/typescript/src/computer/index.ts index e8fde0e5..bbf15224 100644 --- a/libs/computer/typescript/src/computer/index.ts +++ b/libs/computer/typescript/src/computer/index.ts @@ -1,4 +1,3 @@ -import { LumierComputer } from "./providers/lumier"; import type { BaseComputer } from "./providers/base"; import { CloudComputer } from "./providers/cloud"; import { LumeComputer } from "./providers/lume"; @@ -7,7 +6,6 @@ import { type BaseComputerConfig, type CloudComputerConfig, type LumeComputerConfig, - type LumierComputerConfig, } from "./types"; import { applyDefaults } from "./defaults"; @@ -25,7 +23,6 @@ export class Computer { | Partial | Partial | Partial - | Partial ): BaseComputer { // Apply defaults to the configuration const fullConfig = applyDefaults(config); @@ -36,8 +33,6 @@ export class Computer { return new CloudComputer(fullConfig as CloudComputerConfig); case VMProviderType.LUME: return new LumeComputer(fullConfig as LumeComputerConfig); - case VMProviderType.LUMIER: - return new LumierComputer(fullConfig as LumierComputerConfig); default: throw new Error( `Unsupported VM provider type: ${fullConfig.vmProvider}` diff --git a/libs/computer/typescript/src/computer/providers/cloud.ts b/libs/computer/typescript/src/computer/providers/cloud.ts index be318aa8..3a945435 100644 --- a/libs/computer/typescript/src/computer/providers/cloud.ts +++ b/libs/computer/typescript/src/computer/providers/cloud.ts @@ -1,6 +1,8 @@ import { BaseComputer } from "./base"; import type { CloudComputerConfig } from "../types"; -import { computerLogger } from "../../util/logger"; +import pino from "pino"; + +const logger = pino({ name: "cloud" }); /** * Cloud-specific computer implementation @@ -14,7 +16,7 @@ export class CloudComputer extends BaseComputer { * Cloud-specific method to deploy the computer */ async deploy(): Promise { - computerLogger.info(`Deploying cloud computer ${this.name}`); + logger.info(`Deploying cloud computer ${this.name}`); // Cloud-specific implementation } diff --git a/libs/computer/typescript/src/computer/providers/lume.ts b/libs/computer/typescript/src/computer/providers/lume.ts index 9b2d3674..ee95d47b 100644 --- a/libs/computer/typescript/src/computer/providers/lume.ts +++ b/libs/computer/typescript/src/computer/providers/lume.ts @@ -1,3 +1,8 @@ +/** + * WARNING: This file was created as a test/example implementation and is not maintained or expected to work. + * It serves as a reference for how a provider might be implemented but should not be used in production. + */ + import type { Display, LumeComputerConfig } from "../types"; import { BaseComputer } from "./base"; import { applyDefaults } from "../defaults"; diff --git a/libs/computer/typescript/src/computer/providers/lumier.ts b/libs/computer/typescript/src/computer/providers/lumier.ts deleted file mode 100644 index ae18be76..00000000 --- a/libs/computer/typescript/src/computer/providers/lumier.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { BaseComputer } from "./base"; -import { applyDefaults } from "../defaults"; -import type { Display, LumierComputerConfig } from "../types"; -import { computerLogger } from "../../util/logger"; - -/** - * Lumier-specific computer implementation - */ -export class LumierComputer extends BaseComputer { - private display: string | Display; - private memory: string; - private cpu: number; - private image: string; - private sharedDirectories?: string[]; - private noVNCPort?: number; - private storage?: string; - private ephemeral: boolean; - - constructor(config: LumierComputerConfig) { - super(config); - - const defaultConfig = applyDefaults(config); - - this.display = defaultConfig.display; - this.memory = defaultConfig.memory; - this.cpu = defaultConfig.cpu; - this.image = defaultConfig.image; - this.sharedDirectories = defaultConfig.sharedDirectories; - this.noVNCPort = defaultConfig.noVNCPort; - this.storage = defaultConfig.storage; - this.ephemeral = defaultConfig.ephemeral; - } - - /** - * Lumier-specific method to start the container - */ - async startContainer(): Promise { - computerLogger.info( - `Starting Lumier container ${this.name} with ${this.memory} memory and ${this.cpu} CPUs` - ); - computerLogger.info( - `Using image ${this.image} with display ${ - typeof this.display === "string" - ? this.display - : `${this.display.width}x${this.display.height}` - }` - ); - // Lumier-specific implementation - } - - /** - * Lumier-specific method to execute a command in the container - */ - async execCommand(command: string): Promise { - computerLogger.info( - `Executing command in Lumier container ${this.name}: ${command}` - ); - return "command output"; // Example implementation - } -} diff --git a/libs/computer/typescript/src/computer/types.ts b/libs/computer/typescript/src/computer/types.ts index ed2bd02c..27c287e8 100644 --- a/libs/computer/typescript/src/computer/types.ts +++ b/libs/computer/typescript/src/computer/types.ts @@ -24,7 +24,7 @@ export interface BaseComputerConfig { osType: OSType; /** - * The VM provider type to use (lume, lumier, cloud) + * The VM provider type to use (lume, cloud) * @default VMProviderType.LUME */ vmProvider: VMProviderType; @@ -80,7 +80,7 @@ export interface BaseComputerConfig { port?: number; /** - * Optional port for the noVNC web interface (Lumier provider) + * Optional port for the noVNC web interface * @default 8006 */ noVNCPort?: number; @@ -92,7 +92,7 @@ export interface BaseComputerConfig { host?: string; /** - * Optional path for persistent VM storage (Lumier provider) + * Optional path for persistent VM storage */ storage?: string; @@ -132,17 +132,9 @@ export interface LumeComputerConfig extends BaseComputerConfig { vmProvider: VMProviderType.LUME; } -/** - * The Lumier VM provider type - */ -export interface LumierComputerConfig extends BaseComputerConfig { - vmProvider: VMProviderType.LUMIER; -} - export enum VMProviderType { CLOUD = "cloud", LUME = "lume", - LUMIER = "lumier", } export enum OSType { diff --git a/libs/computer/typescript/src/util/logger.ts b/libs/computer/typescript/src/util/logger.ts deleted file mode 100644 index a3ab86cf..00000000 --- a/libs/computer/typescript/src/util/logger.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Shared logger module for the computer library - */ -import pino from "pino"; - -// Create and export default loggers for common components -export const computerLogger = pino({ name: "computer" }); diff --git a/libs/computer/typescript/src/util/lume.ts b/libs/computer/typescript/src/util/lume.ts index cd42e6ba..42da321f 100644 --- a/libs/computer/typescript/src/util/lume.ts +++ b/libs/computer/typescript/src/util/lume.ts @@ -1,8 +1,11 @@ /** - * Shared API utilities for Lume and Lumier providers. + * Shared API utilities for Lume providers. * * This module contains shared functions for interacting with the Lume API, - * used by both the LumeProvider and LumierProvider classes. + * used by LumeProvider. + * + * WARNING: This file was created as a test/example implementation and is not maintained or expected to work. + * It serves as a reference for how a provider might be implemented but should not be used in production. */ import pino from "pino"; @@ -73,7 +76,9 @@ export async function lumeApiGet( } // Construct API URL with encoded storage parameter if needed - const apiUrl = `http://${host}:${port}/lume/vms${vmName ? `/${vmName}` : ""}${storageParam}`; + const apiUrl = `http://${host}:${port}/lume/vms${ + vmName ? `/${vmName}` : "" + }${storageParam}`; // Only print the fetch URL when debug is enabled logger.info(`Executing API request: ${apiUrl}`); diff --git a/libs/computer/typescript/tests/index.test.ts b/libs/computer/typescript/tests/index.test.ts index 36e38c87..c8b56d74 100644 --- a/libs/computer/typescript/tests/index.test.ts +++ b/libs/computer/typescript/tests/index.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import { Computer, OSType, VMProviderType } from "../src/index"; -describe("Create Computer Instances", () => { +describe("Cloud Interface", () => { it("should create a cloud computer", () => { const computer = Computer.create({ vmProvider: VMProviderType.CLOUD, @@ -11,26 +11,4 @@ describe("Create Computer Instances", () => { apiKey: "asdf", }); }); - it("should create a lume computer", () => { - const computer = Computer.create({ - vmProvider: VMProviderType.LUME, - display: { width: 1000, height: 1000, scale_factor: 1 }, - image: "computer-image", - memory: "5GB", - cpu: 2, - name: "computer-name", - osType: OSType.MACOS, - }); - }); - it("should create a lumier computer", () => { - const computer = Computer.create({ - vmProvider: VMProviderType.LUMIER, - display: { width: 1000, height: 1000, scale_factor: 1 }, - image: "computer-image", - memory: "5GB", - cpu: 2, - name: "computer-name", - osType: OSType.MACOS, - }); - }); }); From b8c119b284fa58f6087a0de6088eac961280d9a3 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 18 Jun 2025 10:24:17 -0700 Subject: [PATCH 060/141] Fix logger info --- libs/computer/typescript/src/computer/index.ts | 3 +++ libs/computer/typescript/src/computer/providers/base.ts | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/libs/computer/typescript/src/computer/index.ts b/libs/computer/typescript/src/computer/index.ts index bbf15224..b5a8bb04 100644 --- a/libs/computer/typescript/src/computer/index.ts +++ b/libs/computer/typescript/src/computer/index.ts @@ -8,6 +8,9 @@ import { type LumeComputerConfig, } from "./types"; import { applyDefaults } from "./defaults"; +import pino from "pino"; + +export const logger = pino({ name: "computer" }); /** * Factory class for creating the appropriate Computer instance diff --git a/libs/computer/typescript/src/computer/providers/base.ts b/libs/computer/typescript/src/computer/providers/base.ts index 36211ba3..036af3b6 100644 --- a/libs/computer/typescript/src/computer/providers/base.ts +++ b/libs/computer/typescript/src/computer/providers/base.ts @@ -1,10 +1,10 @@ +import { logger } from "../index"; import type { BaseComputerConfig, Display, OSType, VMProviderType, } from "../types"; -import { computerLogger } from "../../util/logger"; /** * Base Computer class with shared functionality @@ -45,7 +45,7 @@ export abstract class BaseComputer { * Shared method available to all computer types */ async disconnect(): Promise { - computerLogger.info(`Disconnecting from ${this.name}`); + logger.info(`Disconnecting from ${this.name}`); // Implementation would go here } From 0fef183caa5b898ea58f808e42c6578135300efe Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 18 Jun 2025 10:38:28 -0700 Subject: [PATCH 061/141] Create ts workspace for computer --- .vscode/computer-ts.code-workspace | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .vscode/computer-ts.code-workspace diff --git a/.vscode/computer-ts.code-workspace b/.vscode/computer-ts.code-workspace new file mode 100644 index 00000000..fa3d872a --- /dev/null +++ b/.vscode/computer-ts.code-workspace @@ -0,0 +1,8 @@ +{ + "folders": [ + { + "name": "computer-ts", + "path": "../libs/computer/typescript" + } + ], +} \ No newline at end of file From 5c9e2e7b852eeecdf6746e93d101a807508aab1e Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 18 Jun 2025 10:39:25 -0700 Subject: [PATCH 062/141] Update package.json, fix linting issues --- libs/computer/typescript/package.json | 15 +- .../typescript/src/computer/defaults.ts | 2 +- .../computer/typescript/src/computer/index.ts | 12 +- .../typescript/src/computer/providers/base.ts | 9 +- .../src/computer/providers/cloud.ts | 2 + .../typescript/src/computer/providers/lume.ts | 17 +- libs/computer/typescript/src/util/lume.ts | 96 +++--- .../typescript/tests/computer/lume.test.ts | 312 ++++++++++++------ libs/computer/typescript/tests/index.test.ts | 2 +- .../typescript/tests/lume_api.test.ts | 58 +--- 10 files changed, 311 insertions(+), 214 deletions(-) diff --git a/libs/computer/typescript/package.json b/libs/computer/typescript/package.json index 98ea7cf9..5a5167d6 100644 --- a/libs/computer/typescript/package.json +++ b/libs/computer/typescript/package.json @@ -1,20 +1,19 @@ { "name": "@cua/computer", - "version": "0.0.0", + "version": "0.0.1", "packageManager": "pnpm@10.11.0", - "description": "", + "description": "Typescript SDK for c/ua computer interaction", "type": "module", "license": "MIT", "homepage": "", "bugs": { - "url": "" + "url": "https://github.com/trycua/cua/issues" }, "repository": { "type": "git", "url": "git+https://github.com/trycua/cua.git" }, - "author": "", - "funding": "", + "author": "c/ua", "files": [ "dist" ], @@ -29,8 +28,8 @@ "access": "public" }, "scripts": { - "lint": "biome lint --cache .", - "lint:fix": "biome lint --cache --fix .", + "lint": "biome lint .", + "lint:fix": "biome lint --fix .", "build": "tsdown", "dev": "tsdown --watch", "test": "vitest", @@ -52,4 +51,4 @@ "typescript": "^5.8.3", "vitest": "^3.1.3" } -} +} \ No newline at end of file diff --git a/libs/computer/typescript/src/computer/defaults.ts b/libs/computer/typescript/src/computer/defaults.ts index adf0a5c1..bb438019 100644 --- a/libs/computer/typescript/src/computer/defaults.ts +++ b/libs/computer/typescript/src/computer/defaults.ts @@ -1,5 +1,5 @@ import { OSType, VMProviderType } from "./types"; -import type { BaseComputerConfig, Display } from "./types"; +import type { BaseComputerConfig } from "./types"; /** * Default configuration values for Computer diff --git a/libs/computer/typescript/src/computer/index.ts b/libs/computer/typescript/src/computer/index.ts index b5a8bb04..c6501610 100644 --- a/libs/computer/typescript/src/computer/index.ts +++ b/libs/computer/typescript/src/computer/index.ts @@ -15,18 +15,18 @@ export const logger = pino({ name: "computer" }); /** * Factory class for creating the appropriate Computer instance */ -export class Computer { +export const Computer = { /** * Create a computer instance based on the provided configuration * @param config The computer configuration * @returns The appropriate computer instance based on the VM provider type */ - static create( + create: ( config: | Partial | Partial | Partial - ): BaseComputer { + ): BaseComputer => { // Apply defaults to the configuration const fullConfig = applyDefaults(config); @@ -41,7 +41,5 @@ export class Computer { `Unsupported VM provider type: ${fullConfig.vmProvider}` ); } - - throw new Error(`Unsupported VM provider type`); - } -} + }, +}; diff --git a/libs/computer/typescript/src/computer/providers/base.ts b/libs/computer/typescript/src/computer/providers/base.ts index 036af3b6..3176709f 100644 --- a/libs/computer/typescript/src/computer/providers/base.ts +++ b/libs/computer/typescript/src/computer/providers/base.ts @@ -63,8 +63,8 @@ export abstract class BaseComputer { } return { - width: parseInt(match[1], 10), - height: parseInt(match[2], 10), + width: Number.parseInt(match[1], 10), + height: Number.parseInt(match[2], 10), }; } @@ -93,14 +93,13 @@ export abstract class BaseComputer { throw new Error(`Invalid memory format: ${memoryStr}`); } - const value = parseFloat(match[1]); + const value = Number.parseFloat(match[1]); const unit = match[2] || "MB"; // Default to MB if no unit specified // Convert to MB if (unit === "GB") { return Math.round(value * 1024); - } else { - return Math.round(value); } + return Math.round(value); } } diff --git a/libs/computer/typescript/src/computer/providers/cloud.ts b/libs/computer/typescript/src/computer/providers/cloud.ts index 3a945435..612dc7f4 100644 --- a/libs/computer/typescript/src/computer/providers/cloud.ts +++ b/libs/computer/typescript/src/computer/providers/cloud.ts @@ -8,8 +8,10 @@ const logger = pino({ name: "cloud" }); * Cloud-specific computer implementation */ export class CloudComputer extends BaseComputer { + private apiKey: string; constructor(config: CloudComputerConfig) { super(config); + this.apiKey = config.apiKey; } /** diff --git a/libs/computer/typescript/src/computer/providers/lume.ts b/libs/computer/typescript/src/computer/providers/lume.ts index ee95d47b..6e04bad6 100644 --- a/libs/computer/typescript/src/computer/providers/lume.ts +++ b/libs/computer/typescript/src/computer/providers/lume.ts @@ -90,7 +90,7 @@ export class LumeComputer extends BaseComputer { async runVm( image: string, name: string, - runOpts: { [key: string]: any } = {}, + runOpts: { [key: string]: unknown } = {}, storage?: string ): Promise { logger.info( @@ -106,7 +106,7 @@ export class LumeComputer extends BaseComputer { // Lume-specific implementation try { await this.getVm(name, storage); - } catch (e) { + } catch { logger.info( `VM ${name} not found, attempting to pull image ${image} from registry...` ); @@ -161,9 +161,8 @@ export class LumeComputer extends BaseComputer { name: string, image: string, storage?: string, - registry: string = "ghcr.io", - organization: string = "trycua", - pullOpts?: { [key: string]: any } + registry = "ghcr.io", + organization = "trycua" ): Promise { // Validate image parameter if (!image) { @@ -222,7 +221,7 @@ export class LumeComputer extends BaseComputer { */ async updateVm( name: string, - updateOpts: { [key: string]: any }, + updateOpts: { [key: string]: unknown }, storage?: string ): Promise { return await lumeApiUpdate( @@ -239,11 +238,7 @@ export class LumeComputer extends BaseComputer { /** * Lume-specific method to get the IP address of a VM, waiting indefinitely until it's available */ - async getIp( - name: string, - storage?: string, - retryDelay: number = 2 - ): Promise { + async getIp(name: string, storage?: string, retryDelay = 2): Promise { // Track total attempts for logging purposes let attempts = 0; diff --git a/libs/computer/typescript/src/util/lume.ts b/libs/computer/typescript/src/util/lume.ts index 42da321f..cdbd8ce5 100644 --- a/libs/computer/typescript/src/util/lume.ts +++ b/libs/computer/typescript/src/util/lume.ts @@ -40,11 +40,11 @@ export interface VMInfo { } export interface RunOptions { - [key: string]: any; + [key: string]: unknown; } export interface UpdateOptions { - [key: string]: any; + [key: string]: unknown; } /** @@ -63,8 +63,8 @@ export async function lumeApiGet( host: string, port: number, storage?: string, - debug: boolean = false, - verbose: boolean = false + debug = false, + verbose = false ): Promise { // URL encode the storage parameter for the query let storageParam = ""; @@ -111,16 +111,17 @@ export async function lumeApiGet( } return result; - } catch (error: any) { + } catch (err) { + const error = err as Error; let errorMsg = "Unknown error"; if (error.name === "AbortError") { errorMsg = "Operation timeout - the API server is taking too long to respond"; - } else if (error.code === "ECONNREFUSED") { + } else if (error.message.includes("ECONNREFUSED")) { errorMsg = "Failed to connect to the API server - it might still be starting up"; - } else if (error.code === "ENOTFOUND") { + } else if (error.message.includes("ENOTFOUND")) { errorMsg = "Failed to resolve host - check the API server address"; } else if (error.message) { errorMsg = error.message; @@ -149,8 +150,8 @@ export async function lumeApiRun( port: number, runOpts: RunOptions, storage?: string, - debug: boolean = false, - verbose: boolean = false + debug = false, + verbose = false ): Promise { // URL encode the storage parameter for the query let storageParam = ""; @@ -199,14 +200,18 @@ export async function lumeApiRun( } return data; - } catch (error: any) { + } catch (err) { + const error = err as Error; let errorMsg = "Unknown error"; if (error.name === "AbortError") { errorMsg = "Operation timeout - the API server is taking too long to respond"; - } else if (error.code === "ECONNREFUSED") { - errorMsg = "Failed to connect to the API server"; + } else if (error.message.includes("ECONNREFUSED")) { + errorMsg = + "Failed to connect to the API server - it might still be starting up"; + } else if (error.message.includes("ENOTFOUND")) { + errorMsg = "Failed to resolve host - check the API server address"; } else if (error.message) { errorMsg = error.message; } @@ -232,8 +237,8 @@ export async function lumeApiStop( host: string, port: number, storage?: string, - debug: boolean = false, - verbose: boolean = false + debug = false, + verbose = false ): Promise { // URL encode the storage parameter for the query let storageParam = ""; @@ -278,14 +283,18 @@ export async function lumeApiStop( } return data; - } catch (error: any) { + } catch (err) { + const error = err as Error; let errorMsg = "Unknown error"; if (error.name === "AbortError") { errorMsg = "Operation timeout - the API server is taking too long to respond"; - } else if (error.code === "ECONNREFUSED") { - errorMsg = "Failed to connect to the API server"; + } else if (error.message.includes("ECONNREFUSED")) { + errorMsg = + "Failed to connect to the API server - it might still be starting up"; + } else if (error.message.includes("ENOTFOUND")) { + errorMsg = "Failed to resolve host - check the API server address"; } else if (error.message) { errorMsg = error.message; } @@ -313,8 +322,8 @@ export async function lumeApiUpdate( port: number, updateOpts: UpdateOptions, storage?: string, - debug: boolean = false, - verbose: boolean = false + debug = false, + verbose = false ): Promise { // URL encode the storage parameter for the query let storageParam = ""; @@ -363,14 +372,18 @@ export async function lumeApiUpdate( } return data; - } catch (error: any) { + } catch (err) { + const error = err as Error; let errorMsg = "Unknown error"; if (error.name === "AbortError") { errorMsg = "Operation timeout - the API server is taking too long to respond"; - } else if (error.code === "ECONNREFUSED") { - errorMsg = "Failed to connect to the API server"; + } else if (error.message.includes("ECONNREFUSED")) { + errorMsg = + "Failed to connect to the API server - it might still be starting up"; + } else if (error.message.includes("ENOTFOUND")) { + errorMsg = "Failed to resolve host - check the API server address"; } else if (error.message) { errorMsg = error.message; } @@ -400,10 +413,10 @@ export async function lumeApiPull( host: string, port: number, storage?: string, - registry: string = "ghcr.io", - organization: string = "trycua", - debug: boolean = false, - verbose: boolean = false + registry = "ghcr.io", + organization = "trycua", + debug = false, + verbose = false ): Promise { // URL encode the storage parameter for the query let storageParam = ""; @@ -459,13 +472,18 @@ export async function lumeApiPull( } return data; - } catch (error: any) { + } catch (err) { + const error = err as Error; let errorMsg = "Unknown error"; if (error.name === "AbortError") { - errorMsg = "Operation timeout - the pull is taking too long"; - } else if (error.code === "ECONNREFUSED") { - errorMsg = "Failed to connect to the API server"; + errorMsg = + "Operation timeout - the API server is taking too long to respond"; + } else if (error.message.includes("ECONNREFUSED")) { + errorMsg = + "Failed to connect to the API server - it might still be starting up"; + } else if (error.message.includes("ENOTFOUND")) { + errorMsg = "Failed to resolve host - check the API server address"; } else if (error.message) { errorMsg = error.message; } @@ -491,8 +509,8 @@ export async function lumeApiDelete( host: string, port: number, storage?: string, - debug: boolean = false, - verbose: boolean = false + debug = false, + verbose = false ): Promise { // URL encode the storage parameter for the query let storageParam = ""; @@ -537,10 +555,10 @@ export async function lumeApiDelete( // Try to parse JSON response, but handle empty responses let data: VMInfo | null = null; const contentType = response.headers.get("content-type"); - if (contentType && contentType.includes("application/json")) { + if (contentType?.includes("application/json")) { try { data = (await response.json()) as VMInfo; - } catch (e) { + } catch { // Empty response is OK for DELETE } } else { @@ -552,14 +570,18 @@ export async function lumeApiDelete( } return data; - } catch (error: any) { + } catch (err) { + const error = err as Error; let errorMsg = "Unknown error"; if (error.name === "AbortError") { errorMsg = "Operation timeout - the API server is taking too long to respond"; - } else if (error.code === "ECONNREFUSED") { - errorMsg = "Failed to connect to the API server"; + } else if (error.message.includes("ECONNREFUSED")) { + errorMsg = + "Failed to connect to the API server - it might still be starting up"; + } else if (error.message.includes("ENOTFOUND")) { + errorMsg = "Failed to resolve host - check the API server address"; } else if (error.message) { errorMsg = error.message; } diff --git a/libs/computer/typescript/tests/computer/lume.test.ts b/libs/computer/typescript/tests/computer/lume.test.ts index 83d97af3..127e632a 100644 --- a/libs/computer/typescript/tests/computer/lume.test.ts +++ b/libs/computer/typescript/tests/computer/lume.test.ts @@ -86,28 +86,37 @@ describe("LumeComputer", () => { describe("getVm", () => { it("should get VM info successfully", async () => { vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]); - + const computer = new LumeComputer(defaultConfig); const result = await computer.getVm("test-vm"); - - expect(lumeApi.lumeApiGet).toHaveBeenCalledWith("test-vm", "localhost", 7777, undefined); + + expect(lumeApi.lumeApiGet).toHaveBeenCalledWith( + "test-vm", + "localhost", + 7777, + undefined + ); expect(result).toEqual(mockVMInfo); }); it("should handle VM not found error", async () => { vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([]); - + const computer = new LumeComputer(defaultConfig); await expect(computer.getVm("test-vm")).rejects.toThrow("VM Not Found."); }); it("should handle stopped VM state", async () => { - const stoppedVM = { ...mockVMInfo, status: "stopped", ipAddress: undefined }; + const stoppedVM = { + ...mockVMInfo, + status: "stopped", + ipAddress: undefined, + }; vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([stoppedVM]); - + const computer = new LumeComputer(defaultConfig); const result = await computer.getVm("test-vm"); - + expect(result.status).toBe("stopped"); expect(result.name).toBe("test-vm"); }); @@ -115,20 +124,25 @@ describe("LumeComputer", () => { it("should handle VM without IP address", async () => { const noIpVM = { ...mockVMInfo, ipAddress: undefined }; vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([noIpVM]); - + const computer = new LumeComputer(defaultConfig); const result = await computer.getVm("test-vm"); - + expect(result).toEqual(noIpVM); }); it("should pass storage parameter", async () => { vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]); - + const computer = new LumeComputer(defaultConfig); await computer.getVm("test-vm", "/custom/storage"); - - expect(lumeApi.lumeApiGet).toHaveBeenCalledWith("test-vm", "localhost", 7777, "/custom/storage"); + + expect(lumeApi.lumeApiGet).toHaveBeenCalledWith( + "test-vm", + "localhost", + 7777, + "/custom/storage" + ); }); }); @@ -136,10 +150,10 @@ describe("LumeComputer", () => { it("should list all VMs", async () => { const vmList = [mockVMInfo, { ...mockVMInfo, name: "another-vm" }]; vi.mocked(lumeApi.lumeApiGet).mockResolvedValue(vmList); - + const computer = new LumeComputer(defaultConfig); const result = await computer.listVm(); - + expect(lumeApi.lumeApiGet).toHaveBeenCalledWith("", "localhost", 7777); expect(result).toEqual(vmList); }); @@ -149,24 +163,44 @@ describe("LumeComputer", () => { it("should run VM when it already exists", async () => { vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]); vi.mocked(lumeApi.lumeApiRun).mockResolvedValue(mockVMInfo); - + const computer = new LumeComputer(defaultConfig); const runOpts = { memory: "4GB" }; - const result = await computer.runVm("macos-sequoia-cua:latest", "test-vm", runOpts); - - expect(lumeApi.lumeApiGet).toHaveBeenCalledWith("test-vm", "localhost", 7777, undefined); - expect(lumeApi.lumeApiRun).toHaveBeenCalledWith("test-vm", "localhost", 7777, runOpts, undefined); + const result = await computer.runVm( + "macos-sequoia-cua:latest", + "test-vm", + runOpts + ); + + expect(lumeApi.lumeApiGet).toHaveBeenCalledWith( + "test-vm", + "localhost", + 7777, + undefined + ); + expect(lumeApi.lumeApiRun).toHaveBeenCalledWith( + "test-vm", + "localhost", + 7777, + runOpts, + undefined + ); expect(result).toEqual(mockVMInfo); }); it("should pull and run VM when it doesn't exist", async () => { - vi.mocked(lumeApi.lumeApiGet).mockRejectedValueOnce(new Error("VM not found")); + vi.mocked(lumeApi.lumeApiGet).mockRejectedValueOnce( + new Error("VM not found") + ); vi.mocked(lumeApi.lumeApiPull).mockResolvedValue(mockVMInfo); vi.mocked(lumeApi.lumeApiRun).mockResolvedValue(mockVMInfo); - + const computer = new LumeComputer(defaultConfig); - const result = await computer.runVm("macos-sequoia-cua:latest", "test-vm"); - + const result = await computer.runVm( + "macos-sequoia-cua:latest", + "test-vm" + ); + expect(lumeApi.lumeApiGet).toHaveBeenCalled(); expect(lumeApi.lumeApiPull).toHaveBeenCalledWith( "macos-sequoia-cua:latest", @@ -182,33 +216,60 @@ describe("LumeComputer", () => { }); it("should handle pull failure", async () => { - vi.mocked(lumeApi.lumeApiGet).mockRejectedValueOnce(new Error("VM not found")); - vi.mocked(lumeApi.lumeApiPull).mockRejectedValue(new Error("Pull failed")); - + vi.mocked(lumeApi.lumeApiGet).mockRejectedValueOnce( + new Error("VM not found") + ); + vi.mocked(lumeApi.lumeApiPull).mockRejectedValue( + new Error("Pull failed") + ); + const computer = new LumeComputer(defaultConfig); - await expect(computer.runVm("macos-sequoia-cua:latest", "test-vm")).rejects.toThrow("Pull failed"); + await expect( + computer.runVm("macos-sequoia-cua:latest", "test-vm") + ).rejects.toThrow("Pull failed"); }); it("should pass storage parameter", async () => { vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]); vi.mocked(lumeApi.lumeApiRun).mockResolvedValue(mockVMInfo); - + const computer = new LumeComputer(defaultConfig); - await computer.runVm("macos-sequoia-cua:latest", "test-vm", {}, "/storage"); - - expect(lumeApi.lumeApiGet).toHaveBeenCalledWith("test-vm", "localhost", 7777, "/storage"); - expect(lumeApi.lumeApiRun).toHaveBeenCalledWith("test-vm", "localhost", 7777, {}, "/storage"); + await computer.runVm( + "macos-sequoia-cua:latest", + "test-vm", + {}, + "/storage" + ); + + expect(lumeApi.lumeApiGet).toHaveBeenCalledWith( + "test-vm", + "localhost", + 7777, + "/storage" + ); + expect(lumeApi.lumeApiRun).toHaveBeenCalledWith( + "test-vm", + "localhost", + 7777, + {}, + "/storage" + ); }); }); describe("stopVm", () => { it("should stop VM normally", async () => { vi.mocked(lumeApi.lumeApiStop).mockResolvedValue(mockVMInfo); - + const computer = new LumeComputer(defaultConfig); const result = await computer.stopVm("test-vm"); - - expect(lumeApi.lumeApiStop).toHaveBeenCalledWith("test-vm", "localhost", 7777, undefined); + + expect(lumeApi.lumeApiStop).toHaveBeenCalledWith( + "test-vm", + "localhost", + 7777, + undefined + ); expect(result).toEqual(mockVMInfo); }); @@ -216,13 +277,20 @@ describe("LumeComputer", () => { const stoppedVM = { ...mockVMInfo, status: "stopped" }; vi.mocked(lumeApi.lumeApiStop).mockResolvedValue(stoppedVM); vi.mocked(lumeApi.lumeApiDelete).mockResolvedValue(null); - + const ephemeralConfig = { ...defaultConfig, ephemeral: true }; const computer = new LumeComputer(ephemeralConfig); const result = await computer.stopVm("test-vm"); - + expect(lumeApi.lumeApiStop).toHaveBeenCalled(); - expect(lumeApi.lumeApiDelete).toHaveBeenCalledWith("test-vm", "localhost", 7777, undefined, false, false); + expect(lumeApi.lumeApiDelete).toHaveBeenCalledWith( + "test-vm", + "localhost", + 7777, + undefined, + false, + false + ); expect(result).toMatchObject({ ...stoppedVM, deleted: true, @@ -232,34 +300,26 @@ describe("LumeComputer", () => { it("should handle delete failure in ephemeral mode", async () => { vi.mocked(lumeApi.lumeApiStop).mockResolvedValue(mockVMInfo); - vi.mocked(lumeApi.lumeApiDelete).mockRejectedValue(new Error("Delete failed")); - - const ephemeralConfig = { ...defaultConfig, ephemeral: true }; - const computer = new LumeComputer(ephemeralConfig); - - await expect(computer.stopVm("test-vm")).rejects.toThrow("Failed to delete ephemeral VM test-vm: Error: Failed to delete VM: Error: Delete failed"); - }); + vi.mocked(lumeApi.lumeApiDelete).mockRejectedValue( + new Error("Delete failed") + ); - it("should not delete VM if stop returns error in ephemeral mode", async () => { - const errorResponse = { error: "Stop failed" } as any; - vi.mocked(lumeApi.lumeApiStop).mockResolvedValue(errorResponse); - const ephemeralConfig = { ...defaultConfig, ephemeral: true }; const computer = new LumeComputer(ephemeralConfig); - const result = await computer.stopVm("test-vm"); - - expect(lumeApi.lumeApiDelete).not.toHaveBeenCalled(); - expect(result).toEqual(errorResponse); + + await expect(computer.stopVm("test-vm")).rejects.toThrow( + "Failed to delete ephemeral VM test-vm: Error: Failed to delete VM: Error: Delete failed" + ); }); }); describe("pullVm", () => { it("should pull VM image successfully", async () => { vi.mocked(lumeApi.lumeApiPull).mockResolvedValue(mockVMInfo); - + const computer = new LumeComputer(defaultConfig); const result = await computer.pullVm("test-vm", "ubuntu:latest"); - + expect(lumeApi.lumeApiPull).toHaveBeenCalledWith( "ubuntu:latest", "test-vm", @@ -274,15 +334,23 @@ describe("LumeComputer", () => { it("should throw error if image parameter is missing", async () => { const computer = new LumeComputer(defaultConfig); - await expect(computer.pullVm("test-vm", "")).rejects.toThrow("Image parameter is required for pullVm"); + await expect(computer.pullVm("test-vm", "")).rejects.toThrow( + "Image parameter is required for pullVm" + ); }); it("should use custom registry and organization", async () => { vi.mocked(lumeApi.lumeApiPull).mockResolvedValue(mockVMInfo); - + const computer = new LumeComputer(defaultConfig); - await computer.pullVm("test-vm", "custom:tag", "/storage", "docker.io", "myorg"); - + await computer.pullVm( + "test-vm", + "custom:tag", + "/storage", + "docker.io", + "myorg" + ); + expect(lumeApi.lumeApiPull).toHaveBeenCalledWith( "custom:tag", "test-vm", @@ -295,38 +363,60 @@ describe("LumeComputer", () => { }); it("should handle pull failure", async () => { - vi.mocked(lumeApi.lumeApiPull).mockRejectedValue(new Error("Network error")); - + vi.mocked(lumeApi.lumeApiPull).mockRejectedValue( + new Error("Network error") + ); + const computer = new LumeComputer(defaultConfig); - await expect(computer.pullVm("test-vm", "ubuntu:latest")).rejects.toThrow("Failed to pull VM: Error: Network error"); + await expect(computer.pullVm("test-vm", "ubuntu:latest")).rejects.toThrow( + "Failed to pull VM: Error: Network error" + ); }); }); describe("deleteVm", () => { it("should delete VM successfully", async () => { vi.mocked(lumeApi.lumeApiDelete).mockResolvedValue(null); - + const computer = new LumeComputer(defaultConfig); const result = await computer.deleteVm("test-vm"); - - expect(lumeApi.lumeApiDelete).toHaveBeenCalledWith("test-vm", "localhost", 7777, undefined, false, false); + + expect(lumeApi.lumeApiDelete).toHaveBeenCalledWith( + "test-vm", + "localhost", + 7777, + undefined, + false, + false + ); expect(result).toBeNull(); }); it("should handle delete failure", async () => { - vi.mocked(lumeApi.lumeApiDelete).mockRejectedValue(new Error("Permission denied")); - + vi.mocked(lumeApi.lumeApiDelete).mockRejectedValue( + new Error("Permission denied") + ); + const computer = new LumeComputer(defaultConfig); - await expect(computer.deleteVm("test-vm")).rejects.toThrow("Failed to delete VM: Error: Permission denied"); + await expect(computer.deleteVm("test-vm")).rejects.toThrow( + "Failed to delete VM: Error: Permission denied" + ); }); it("should pass storage parameter", async () => { vi.mocked(lumeApi.lumeApiDelete).mockResolvedValue(null); - + const computer = new LumeComputer(defaultConfig); await computer.deleteVm("test-vm", "/storage"); - - expect(lumeApi.lumeApiDelete).toHaveBeenCalledWith("test-vm", "localhost", 7777, "/storage", false, false); + + expect(lumeApi.lumeApiDelete).toHaveBeenCalledWith( + "test-vm", + "localhost", + 7777, + "/storage", + false, + false + ); }); }); @@ -334,11 +424,11 @@ describe("LumeComputer", () => { it("should update VM configuration", async () => { const updatedVM = { ...mockVMInfo, memorySize: 4096 }; vi.mocked(lumeApi.lumeApiUpdate).mockResolvedValue(updatedVM); - + const computer = new LumeComputer(defaultConfig); const updateOpts = { memory: "4GB" }; const result = await computer.updateVm("test-vm", updateOpts); - + expect(lumeApi.lumeApiUpdate).toHaveBeenCalledWith( "test-vm", "localhost", @@ -353,10 +443,10 @@ describe("LumeComputer", () => { it("should pass storage parameter", async () => { vi.mocked(lumeApi.lumeApiUpdate).mockResolvedValue(mockVMInfo); - + const computer = new LumeComputer(defaultConfig); await computer.updateVm("test-vm", {}, "/storage"); - + expect(lumeApi.lumeApiUpdate).toHaveBeenCalledWith( "test-vm", "localhost", @@ -372,10 +462,10 @@ describe("LumeComputer", () => { describe("getIp", () => { it("should return IP address immediately if available", async () => { vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]); - + const computer = new LumeComputer(defaultConfig); const ip = await computer.getIp("test-vm"); - + expect(ip).toBe("192.168.1.100"); expect(lumeApi.lumeApiGet).toHaveBeenCalledTimes(1); }); @@ -386,18 +476,22 @@ describe("LumeComputer", () => { .mockResolvedValueOnce([noIpVM]) .mockResolvedValueOnce([noIpVM]) .mockResolvedValueOnce([mockVMInfo]); - + const computer = new LumeComputer(defaultConfig); const ip = await computer.getIp("test-vm", undefined, 0.1); // Short retry delay for testing - + expect(ip).toBe("192.168.1.100"); expect(lumeApi.lumeApiGet).toHaveBeenCalledTimes(3); }); it("should throw error if VM is stopped", async () => { - const stoppedVM = { ...mockVMInfo, status: "stopped", ipAddress: undefined }; + const stoppedVM = { + ...mockVMInfo, + status: "stopped", + ipAddress: undefined, + }; vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([stoppedVM]); - + const computer = new LumeComputer(defaultConfig); await expect(computer.getIp("test-vm")).rejects.toThrow( "VM test-vm is in 'stopped' state and will not get an IP address" @@ -407,7 +501,7 @@ describe("LumeComputer", () => { it("should throw error if VM is in error state", async () => { const errorVM = { ...mockVMInfo, status: "error", ipAddress: undefined }; vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([errorVM]); - + const computer = new LumeComputer(defaultConfig); await expect(computer.getIp("test-vm")).rejects.toThrow( "VM test-vm is in 'error' state and will not get an IP address" @@ -415,48 +509,61 @@ describe("LumeComputer", () => { }); it("should handle getVm errors", async () => { - vi.mocked(lumeApi.lumeApiGet).mockRejectedValue(new Error("Network error")); - + vi.mocked(lumeApi.lumeApiGet).mockRejectedValue( + new Error("Network error") + ); + const computer = new LumeComputer(defaultConfig); await expect(computer.getIp("test-vm")).rejects.toThrow("Network error"); }); it("should pass storage parameter", async () => { vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]); - + const computer = new LumeComputer(defaultConfig); await computer.getIp("test-vm", "/storage"); - - expect(lumeApi.lumeApiGet).toHaveBeenCalledWith("test-vm", "localhost", 7777, "/storage"); + + expect(lumeApi.lumeApiGet).toHaveBeenCalledWith( + "test-vm", + "localhost", + 7777, + "/storage" + ); }); }); describe("integration scenarios", () => { it("should handle full VM lifecycle", async () => { // Simulate VM not existing initially - vi.mocked(lumeApi.lumeApiGet).mockRejectedValueOnce(new Error("VM not found")); + vi.mocked(lumeApi.lumeApiGet).mockRejectedValueOnce( + new Error("VM not found") + ); vi.mocked(lumeApi.lumeApiPull).mockResolvedValue(mockVMInfo); - + // Simulate VM starting without IP, then getting IP - const startingVM = { ...mockVMInfo, ipAddress: undefined, status: "starting" }; + const startingVM = { + ...mockVMInfo, + ipAddress: undefined, + status: "starting", + }; vi.mocked(lumeApi.lumeApiRun).mockResolvedValue(startingVM); vi.mocked(lumeApi.lumeApiGet) .mockResolvedValueOnce([startingVM]) .mockResolvedValueOnce([mockVMInfo]); - + // Simulate stop const stoppedVM = { ...mockVMInfo, status: "stopped" }; vi.mocked(lumeApi.lumeApiStop).mockResolvedValue(stoppedVM); - + const computer = new LumeComputer(defaultConfig); - + // Run VM (should pull first) await computer.runVm("macos-sequoia-cua:latest", "test-vm"); - + // Get IP (should retry once) const ip = await computer.getIp("test-vm", undefined, 0.1); expect(ip).toBe("192.168.1.100"); - + // Stop VM const stopResult = await computer.stopVm("test-vm"); expect(stopResult.status).toBe("stopped"); @@ -465,20 +572,23 @@ describe("LumeComputer", () => { it("should handle ephemeral VM lifecycle", async () => { const ephemeralConfig = { ...defaultConfig, ephemeral: true }; const computer = new LumeComputer(ephemeralConfig); - + // Setup mocks vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]); vi.mocked(lumeApi.lumeApiRun).mockResolvedValue(mockVMInfo); - vi.mocked(lumeApi.lumeApiStop).mockResolvedValue({ ...mockVMInfo, status: "stopped" }); + vi.mocked(lumeApi.lumeApiStop).mockResolvedValue({ + ...mockVMInfo, + status: "stopped", + }); vi.mocked(lumeApi.lumeApiDelete).mockResolvedValue(null); - + // Run and stop ephemeral VM await computer.runVm("macos-sequoia-cua:latest", "test-vm"); const result = await computer.stopVm("test-vm"); - + // Verify VM was deleted expect(lumeApi.lumeApiDelete).toHaveBeenCalled(); - expect((result as any).deleted).toBe(true); + expect(result).toBe(null); }); }); -}); \ No newline at end of file +}); diff --git a/libs/computer/typescript/tests/index.test.ts b/libs/computer/typescript/tests/index.test.ts index c8b56d74..5d866ba4 100644 --- a/libs/computer/typescript/tests/index.test.ts +++ b/libs/computer/typescript/tests/index.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, it } from "vitest"; import { Computer, OSType, VMProviderType } from "../src/index"; describe("Cloud Interface", () => { diff --git a/libs/computer/typescript/tests/lume_api.test.ts b/libs/computer/typescript/tests/lume_api.test.ts index 43d4ddbb..e6b28802 100644 --- a/libs/computer/typescript/tests/lume_api.test.ts +++ b/libs/computer/typescript/tests/lume_api.test.ts @@ -1,4 +1,4 @@ -import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; +import { describe, expect, it, vi } from "vitest"; import { lumeApiGet, lumeApiRun, @@ -15,24 +15,6 @@ const PORT = 1213; const HOST = "localhost"; describe("Lume API", () => { - let lumeServer: any; - - beforeAll(() => { - // Spawn the lume serve process before tests - const { spawn } = require("child_process"); - lumeServer = spawn("lume", ["serve", "--port", PORT], { - stdio: "pipe", - detached: true, - }); - - // Clean up the server when tests are done - afterAll(() => { - if (lumeServer && !lumeServer.killed) { - process.kill(-lumeServer.pid); - } - }); - }); - describe("lumeApiGet", () => { it("should fetch VM information successfully", async () => { // Mock fetch for this test - API returns a single VMDetails object @@ -44,7 +26,7 @@ describe("Lume API", () => { os: "ubuntu", display: "1920x1080", locationName: "local", - cpuCount: 2, + cpuCount: 2, sharedDirectories: [ { hostPath: "/home/user/shared", @@ -56,7 +38,7 @@ describe("Lume API", () => { global.fetch = vi.fn().mockResolvedValueOnce({ ok: true, - json: async () => mockVMInfo, + json: async () => mockVMInfo, headers: new Headers({ "content-type": "application/json" }), } as Response); @@ -99,7 +81,7 @@ describe("Lume API", () => { global.fetch = vi.fn().mockResolvedValueOnce({ ok: true, - json: async () => mockVMList, + json: async () => mockVMList, headers: new Headers({ "content-type": "application/json" }), } as Response); @@ -161,7 +143,7 @@ describe("Lume API", () => { it("should handle connection refused errors", async () => { const error = new Error("Connection refused"); - (error as any).code = "ECONNREFUSED"; + (error as Error).message = "ECONNREFUSED"; global.fetch = vi.fn().mockRejectedValueOnce(error); await expect(lumeApiGet("test-vm", HOST, PORT)).rejects.toThrow( @@ -181,7 +163,7 @@ describe("Lume API", () => { it("should handle host not found errors", async () => { const error = new Error("Host not found"); - (error as any).code = "ENOTFOUND"; + (error as Error).message = "ENOTFOUND"; global.fetch = vi.fn().mockRejectedValueOnce(error); await expect(lumeApiGet("test-vm", HOST, PORT)).rejects.toThrow( @@ -200,7 +182,7 @@ describe("Lume API", () => { os: "ubuntu", display: "1920x1080", locationName: "local", - cpuCount: 2, + cpuCount: 2, vncUrl: "vnc://localhost:5900", ipAddress: "192.168.1.100", }; @@ -263,9 +245,7 @@ describe("Lume API", () => { headers: new Headers(), } as Response); - await expect( - lumeApiRun("test-vm", HOST, PORT, {}) - ).rejects.toThrow( + await expect(lumeApiRun("test-vm", HOST, PORT, {})).rejects.toThrow( "API request failed: HTTP error returned from API server (status: 500)" ); }); @@ -281,7 +261,7 @@ describe("Lume API", () => { os: "ubuntu", display: "1920x1080", locationName: "local", - cpuCount: 2, + cpuCount: 2, }; global.fetch = vi.fn().mockResolvedValueOnce({ @@ -338,7 +318,7 @@ describe("Lume API", () => { os: "ubuntu", display: "2560x1440", locationName: "local", - cpuCount: 2, + cpuCount: 2, }; global.fetch = vi.fn().mockResolvedValueOnce({ @@ -396,7 +376,7 @@ describe("Lume API", () => { os: "ubuntu", display: "1920x1080", locationName: "local", - cpuCount: 2, + cpuCount: 2, }; global.fetch = vi.fn().mockResolvedValueOnce({ @@ -493,7 +473,7 @@ describe("Lume API", () => { os: "ubuntu", display: "", locationName: "local", - cpuCount: 0, + cpuCount: 0, }; global.fetch = vi.fn().mockResolvedValueOnce({ @@ -543,7 +523,7 @@ describe("Lume API", () => { } as Response); const result = await lumeApiDelete("non-existent-vm", HOST, PORT); - + expect(result).toBeNull(); }); @@ -566,7 +546,7 @@ describe("Lume API", () => { global.fetch = vi.fn().mockResolvedValueOnce({ ok: true, - json: async () => [{ name: "test-vm", cpuCount: 2 }], + json: async () => [{ name: "test-vm", cpuCount: 2 }], headers: new Headers({ "content-type": "application/json" }), } as Response); @@ -584,7 +564,7 @@ describe("Lume API", () => { global.fetch = vi.fn().mockResolvedValueOnce({ ok: true, - json: async () => ({ name: "test-vm", cpuCount: 2 }), + json: async () => ({ name: "test-vm", cpuCount: 2 }), headers: new Headers({ "content-type": "application/json" }), } as Response); @@ -613,13 +593,5 @@ describe("Lume API", () => { "API request failed: Custom error message" ); }); - - it("should handle unknown errors", async () => { - global.fetch = vi.fn().mockRejectedValueOnce({}); - - await expect(lumeApiGet("test-vm", HOST, PORT)).rejects.toThrow( - "API request failed: Unknown error" - ); - }); }); }); From 0ffea156fc2c449d059e9b092615c9e831184e0d Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 18 Jun 2025 10:39:37 -0700 Subject: [PATCH 063/141] Linter: ignore dist. --- libs/computer/typescript/biome.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/computer/typescript/biome.json b/libs/computer/typescript/biome.json index 221019d2..3251a52d 100644 --- a/libs/computer/typescript/biome.json +++ b/libs/computer/typescript/biome.json @@ -8,7 +8,7 @@ "files": { "ignoreUnknown": false, "ignore": [ - "build", + "dist", "node_modules" ] }, From e7a6d684387bdc531097e756a181f4875b8b650b Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 18 Jun 2025 10:48:05 -0700 Subject: [PATCH 064/141] update readme --- libs/computer/typescript/README.md | 46 ++++++++++-------------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/libs/computer/typescript/README.md b/libs/computer/typescript/README.md index 5ed0f1ac..6babcac6 100644 --- a/libs/computer/typescript/README.md +++ b/libs/computer/typescript/README.md @@ -4,12 +4,7 @@ The TypeScript library for C/UA Computer - a powerful computer control and autom ## Overview -This library is a TypeScript port of the Python computer library, providing the same functionality for controlling virtual machines and computer interfaces. It includes: - -- **Computer Class**: Main class for interacting with computers (virtual or host) -- **VM Providers**: Support for different VM providers (Lume, Lumier, Cloud) -- **Computer Interfaces**: OS-specific interfaces for controlling computers (macOS, Linux, Windows) -- **Utilities**: Helper functions for display parsing, memory parsing, logging, and telemetry +This library is a TypeScript port of the Python computer library, providing the same functionality for controlling virtual machines and computer interfaces. It enables programmatic control of virtual machines through various providers and offers a consistent interface for interacting with the VM's operating system. ## Installation @@ -54,31 +49,23 @@ await computer.stop(); ## Architecture -The library is organized into several key modules: +The library is organized into the following structure: ### Core Components -- `Computer`: Main class that manages VM lifecycle and interfaces -- `ComputerOptions`: Configuration options for computer instances -### Models -- `Display`: Display configuration (width, height) -- `ComputerConfig`: Internal computer configuration +- **Computer Factory**: A factory object that creates appropriate computer instances +- **BaseComputer**: Abstract base class with shared functionality for all computer types +- **Types**: Type definitions for configuration options and shared interfaces -### Providers -- `BaseVMProvider`: Abstract base class for VM providers -- `VMProviderFactory`: Factory for creating provider instances -- Provider types: `LUME`, `LUMIER`, `CLOUD` +### Provider Implementations -### Interfaces -- `BaseComputerInterface`: Abstract base class for OS interfaces -- `InterfaceFactory`: Factory for creating OS-specific interfaces -- Interface models: Key types, mouse buttons, accessibility tree +- **LumeComputer**: Implementation for Lume API-based VMs +- **CloudComputer**: Implementation for cloud-based VMs -### Utilities -- `Logger`: Logging with different verbosity levels -- `helpers`: Default computer management and sandboxed execution -- `utils`: Display/memory parsing, timeout utilities -- `telemetry`: Usage tracking and metrics +### Utility Functions + +- **Lume API Utilities**: Functions for interacting with the Lume API (lumeApiGet, lumeApiRun, lumeApiStop, etc.) +- **Helper Functions**: Parsing utilities for display and memory strings ## Development @@ -106,13 +93,10 @@ pnpm build pnpm typecheck ``` -## External Dependencies +## Disclaimer + +**WARNING:** Some parts of this library, particularly the provider implementations (like Lume), were created as test/example implementations and are not maintained or expected to work in production environments. They serve as references for how providers might be implemented but should not be used in production. -- `sharp`: For image processing and screenshot manipulation -- Additional provider-specific packages need to be installed separately: - - `@cua/computer-lume`: For Lume provider support - - `@cua/computer-lumier`: For Lumier provider support - - `@cua/computer-cloud`: For Cloud provider support ## License From a77861da47a83f48c507c5d2a171c487b9f463f3 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 18 Jun 2025 14:16:17 -0400 Subject: [PATCH 065/141] Docker-based quickstart --- README.md | 10 +- scripts/playground-docker.sh | 299 +++++++++++++++++++++++++++++++++++ 2 files changed, 304 insertions(+), 5 deletions(-) create mode 100644 scripts/playground-docker.sh diff --git a/README.md b/README.md index cf1e8004..041de65e 100644 --- a/README.md +++ b/README.md @@ -53,15 +53,15 @@ -### Option 1: Fully-managed install (recommended) -*Guided install for quick use* +### Option 1: Fully-managed install with Docker (recommended) +*Docker-based guided install for quick use* **macOS/Linux/Windows (via WSL):** ```bash -# Requires Python 3.11+ -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/scripts/playground.sh)" +# Requires Docker +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/scripts/playground-docker.sh)" ``` -This script will guide you through setup and launch the Computer-Use Agent UI. +This script will guide you through setup using Docker containers and launch the Computer-Use Agent UI. --- diff --git a/scripts/playground-docker.sh b/scripts/playground-docker.sh new file mode 100644 index 00000000..59687eb4 --- /dev/null +++ b/scripts/playground-docker.sh @@ -0,0 +1,299 @@ +#!/bin/bash + +set -e + +# Colors for output +GREEN='\033[0;32m' +BLUE='\033[0;34m' +RED='\033[0;31m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Print with color +print_info() { + echo -e "${BLUE}==> $1${NC}" +} + +print_success() { + echo -e "${GREEN}==> $1${NC}" +} + +print_error() { + echo -e "${RED}==> $1${NC}" +} + +print_warning() { + echo -e "${YELLOW}==> $1${NC}" +} + +echo "🚀 Launching C/ua Computer-Use Agent UI..." + +# Check if Docker is installed +if ! command -v docker &> /dev/null; then + print_error "Docker is not installed!" + echo "" + echo "To use C/ua with Docker containers, you need to install Docker first:" + echo "" + echo "📦 Install Docker:" + echo " • macOS: Download Docker Desktop from https://docker.com/products/docker-desktop" + echo " • Windows: Download Docker Desktop from https://docker.com/products/docker-desktop" + echo " • Linux: Follow instructions at https://docs.docker.com/engine/install/" + echo "" + echo "After installing Docker, run this script again." + exit 1 +fi + +# Check if Docker daemon is running +if ! docker info &> /dev/null; then + print_error "Docker is installed but not running!" + echo "" + echo "Please start Docker Desktop and try again." + exit 1 +fi + +print_success "Docker is installed and running!" + +# Save the original working directory +ORIGINAL_DIR="$(pwd)" + +# Directories used by the script +DEMO_DIR="$HOME/.cua" +REPO_DIR="$DEMO_DIR/cua" + +# Function to clean up on exit +cleanup() { + cd "$ORIGINAL_DIR" 2>/dev/null || true +} +trap cleanup EXIT + +echo "" +echo "Choose your C/ua setup:" +echo "1) ☁️ C/ua Cloud Containers (works on any system)" +echo "2) 🖥️ Local macOS VMs (requires Apple Silicon Mac + macOS 15+)" +echo "3) 🖥️ Local Windows VMs (requires Windows 10 / 11)" +echo "" +read -p "Enter your choice (1, 2, or 3): " CHOICE + +if [[ "$CHOICE" == "1" ]]; then + # C/ua Cloud Container setup + echo "" + print_info "Setting up C/ua Cloud Containers..." + echo "" + + # Check if existing .env.local already has CUA_API_KEY + REPO_ENV_FILE="$REPO_DIR/.env.local" + CURRENT_ENV_FILE="$ORIGINAL_DIR/.env.local" + + CUA_API_KEY="" + + # First check current directory + if [[ -f "$CURRENT_ENV_FILE" ]] && grep -q "CUA_API_KEY=" "$CURRENT_ENV_FILE"; then + EXISTING_CUA_KEY=$(grep "CUA_API_KEY=" "$CURRENT_ENV_FILE" | cut -d'=' -f2- | tr -d '"' | tr -d "'" | xargs) + if [[ -n "$EXISTING_CUA_KEY" && "$EXISTING_CUA_KEY" != "your_cua_api_key_here" && "$EXISTING_CUA_KEY" != "" ]]; then + CUA_API_KEY="$EXISTING_CUA_KEY" + fi + fi + + # Then check repo directory if not found in current dir + if [[ -z "$CUA_API_KEY" ]] && [[ -f "$REPO_ENV_FILE" ]] && grep -q "CUA_API_KEY=" "$REPO_ENV_FILE"; then + EXISTING_CUA_KEY=$(grep "CUA_API_KEY=" "$REPO_ENV_FILE" | cut -d'=' -f2- | tr -d '"' | tr -d "'" | xargs) + if [[ -n "$EXISTING_CUA_KEY" && "$EXISTING_CUA_KEY" != "your_cua_api_key_here" && "$EXISTING_CUA_KEY" != "" ]]; then + CUA_API_KEY="$EXISTING_CUA_KEY" + fi + fi + + # If no valid API key found, prompt for one + if [[ -z "$CUA_API_KEY" ]]; then + echo "To use C/ua Cloud Containers, you need to:" + echo "1. Sign up at https://trycua.com" + echo "2. Create a Cloud Container" + echo "3. Generate an Api Key" + echo "" + read -p "Enter your C/ua Api Key: " CUA_API_KEY + + if [[ -z "$CUA_API_KEY" ]]; then + print_error "C/ua Api Key is required for Cloud Containers." + exit 1 + fi + else + print_success "Found existing CUA API key" + fi + + USE_CLOUD=true + COMPUTER_TYPE="cloud" + +elif [[ "$CHOICE" == "2" ]]; then + # Local macOS VM setup + echo "" + print_info "Setting up local macOS VMs..." + + # Check for Apple Silicon Mac + if [[ $(uname -s) != "Darwin" || $(uname -m) != "arm64" ]]; then + print_error "Local macOS VMs require an Apple Silicon Mac (M1/M2/M3/M4)." + echo "💡 Consider using C/ua Cloud Containers instead (option 1)." + exit 1 + fi + + # Check for macOS 15 (Sequoia) or newer + OSVERSION=$(sw_vers -productVersion) + if [[ $(echo "$OSVERSION 15.0" | tr " " "\n" | sort -V | head -n 1) != "15.0" ]]; then + print_error "Local macOS VMs require macOS 15 (Sequoia) or newer. You have $OSVERSION." + echo "💡 Consider using C/ua Cloud Containers instead (option 1)." + exit 1 + fi + + USE_CLOUD=false + COMPUTER_TYPE="macos" + +elif [[ "$CHOICE" == "3" ]]; then + # Local Windows VM setup + echo "" + print_info "Setting up local Windows VMs..." + + # Check if we're on Windows + if [[ $(uname -s) != MINGW* && $(uname -s) != CYGWIN* && $(uname -s) != MSYS* ]]; then + print_error "Local Windows VMs require Windows 10 or 11." + echo "💡 Consider using C/ua Cloud Containers instead (option 1)." + echo "" + echo "🔗 If you are using WSL, refer to the blog post to get started: https://www.trycua.com/blog/windows-sandbox" + exit 1 + fi + + USE_CLOUD=false + COMPUTER_TYPE="windows" + +else + print_error "Invalid choice. Please run the script again and choose 1, 2, or 3." + exit 1 +fi + +# Create demo directory +mkdir -p "$DEMO_DIR" + +# Clone or update the repository +if [[ ! -d "$REPO_DIR" ]]; then + print_info "Cloning C/ua repository..." + cd "$DEMO_DIR" + git clone https://github.com/trycua/cua.git +else + print_info "Updating C/ua repository..." + cd "$REPO_DIR" + git pull origin main +fi + +cd "$REPO_DIR" + +# Create .env.local file with API keys +ENV_FILE="$REPO_DIR/.env.local" +if [[ ! -f "$ENV_FILE" ]]; then + cat > "$ENV_FILE" << EOF +# Uncomment and add your API keys here +# OPENAI_API_KEY=your_openai_api_key_here +# ANTHROPIC_API_KEY=your_anthropic_api_key_here +CUA_API_KEY=your_cua_api_key_here +EOF + print_success "Created .env.local file with API key placeholders" +else + print_success "Found existing .env.local file - keeping your current settings" +fi + +if [[ "$USE_CLOUD" == "true" ]]; then + # Add CUA API key to .env.local if not already present + if ! grep -q "CUA_API_KEY" "$ENV_FILE"; then + echo "CUA_API_KEY=$CUA_API_KEY" >> "$ENV_FILE" + print_success "Added CUA_API_KEY to .env.local" + elif grep -q "CUA_API_KEY=your_cua_api_key_here" "$ENV_FILE"; then + # Update placeholder with actual key + sed -i.bak "s/CUA_API_KEY=your_cua_api_key_here/CUA_API_KEY=$CUA_API_KEY/" "$ENV_FILE" + print_success "Updated CUA_API_KEY in .env.local" + fi +fi + +# Build the Docker image if it doesn't exist +print_info "Checking Docker image..." +if ! docker image inspect cua-dev-image &> /dev/null; then + print_info "Building Docker image (this may take a while)..." + ./scripts/run-docker-dev.sh build +else + print_success "Docker image already exists" +fi + +# Install Lume if needed for local VMs +if [[ "$USE_CLOUD" == "false" && "$COMPUTER_TYPE" == "macos" ]]; then + if ! command -v lume &> /dev/null; then + print_info "Installing Lume CLI..." + curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/libs/lume/scripts/install.sh | bash + + # Add lume to PATH for this session if it's not already there + if ! command -v lume &> /dev/null; then + export PATH="$PATH:$HOME/.local/bin" + fi + fi + + # Pull the macOS CUA image if not already present + if ! lume ls | grep -q "macos-sequoia-cua"; then + # Check available disk space + IMAGE_SIZE_GB=30 + AVAILABLE_SPACE_KB=$(df -k $HOME | tail -1 | awk '{print $4}') + AVAILABLE_SPACE_GB=$(($AVAILABLE_SPACE_KB / 1024 / 1024)) + + echo "📊 The macOS CUA image will use approximately ${IMAGE_SIZE_GB}GB of disk space." + echo " You currently have ${AVAILABLE_SPACE_GB}GB available on your system." + + # Prompt for confirmation + read -p " Continue? [y]/n: " CONTINUE + CONTINUE=${CONTINUE:-y} + + if [[ $CONTINUE =~ ^[Yy]$ ]]; then + print_info "Pulling macOS CUA image (this may take a while)..." + lume pull macos-sequoia-cua:latest + else + print_error "Installation cancelled." + exit 1 + fi + fi + + # Check if the VM is running + print_info "Checking if the macOS CUA VM is running..." + VM_RUNNING=$(lume ls | grep "macos-sequoia-cua" | grep "running" || echo "") + + if [ -z "$VM_RUNNING" ]; then + print_info "Starting the macOS CUA VM in the background..." + lume run macos-sequoia-cua:latest & + # Wait a moment for the VM to initialize + sleep 5 + print_success "VM started successfully." + else + print_success "macOS CUA VM is already running." + fi +fi + +# Create a convenience script to run the demo +cat > "$DEMO_DIR/start_ui.sh" << EOF +#!/bin/bash +cd "$REPO_DIR" +./scripts/run-docker-dev.sh run agent_ui_examples.py +EOF +chmod +x "$DEMO_DIR/start_ui.sh" + +print_success "Setup complete!" + +if [[ "$USE_CLOUD" == "true" ]]; then + echo "☁️ C/ua Cloud Container setup complete!" +else + echo "🖥️ C/ua Local VM setup complete!" +fi + +echo "📝 Edit $ENV_FILE to update your API keys" +echo "🖥️ Start the playground by running: $DEMO_DIR/start_ui.sh" + +# Start the demo automatically +echo +print_info "Starting the C/ua Computer-Use Agent UI..." +echo "" + +print_success "C/ua Computer-Use Agent UI is now running at http://localhost:7860/" +echo +echo "🌐 Open your browser and go to: http://localhost:7860/" +echo +"$DEMO_DIR/start_ui.sh" \ No newline at end of file From 6bbeac9777343b0b2d58daa0eebd0ed9667d2143 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 18 Jun 2025 15:39:54 -0400 Subject: [PATCH 066/141] Use caffeinate on macOS to prevent system sleep during the pull --- scripts/playground-docker.sh | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/scripts/playground-docker.sh b/scripts/playground-docker.sh index 59687eb4..191878c5 100644 --- a/scripts/playground-docker.sh +++ b/scripts/playground-docker.sh @@ -246,7 +246,14 @@ if [[ "$USE_CLOUD" == "false" && "$COMPUTER_TYPE" == "macos" ]]; then if [[ $CONTINUE =~ ^[Yy]$ ]]; then print_info "Pulling macOS CUA image (this may take a while)..." - lume pull macos-sequoia-cua:latest + + # Use caffeinate on macOS to prevent system sleep during the pull + if command -v caffeinate &> /dev/null; then + print_info "Using caffeinate to prevent system sleep during download..." + caffeinate -i lume pull macos-sequoia-cua:latest + else + lume pull macos-sequoia-cua:latest + fi else print_error "Installation cancelled." exit 1 @@ -296,4 +303,4 @@ print_success "C/ua Computer-Use Agent UI is now running at http://localhost:786 echo echo "🌐 Open your browser and go to: http://localhost:7860/" echo -"$DEMO_DIR/start_ui.sh" \ No newline at end of file +"$DEMO_DIR/start_ui.sh" From b3eeb0c9f573f3dcf8e38e5c6e5db8b5fa41689b Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 18 Jun 2025 13:13:51 -0700 Subject: [PATCH 067/141] Fix issue with previous merge --- .../{ => python}/computer/providers/winsandbox/__init__.py | 0 .../{ => python}/computer/providers/winsandbox/provider.py | 0 .../{ => python}/computer/providers/winsandbox/setup_script.ps1 | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename libs/computer/{ => python}/computer/providers/winsandbox/__init__.py (100%) rename libs/computer/{ => python}/computer/providers/winsandbox/provider.py (100%) rename libs/computer/{ => python}/computer/providers/winsandbox/setup_script.ps1 (100%) diff --git a/libs/computer/computer/providers/winsandbox/__init__.py b/libs/computer/python/computer/providers/winsandbox/__init__.py similarity index 100% rename from libs/computer/computer/providers/winsandbox/__init__.py rename to libs/computer/python/computer/providers/winsandbox/__init__.py diff --git a/libs/computer/computer/providers/winsandbox/provider.py b/libs/computer/python/computer/providers/winsandbox/provider.py similarity index 100% rename from libs/computer/computer/providers/winsandbox/provider.py rename to libs/computer/python/computer/providers/winsandbox/provider.py diff --git a/libs/computer/computer/providers/winsandbox/setup_script.ps1 b/libs/computer/python/computer/providers/winsandbox/setup_script.ps1 similarity index 100% rename from libs/computer/computer/providers/winsandbox/setup_script.ps1 rename to libs/computer/python/computer/providers/winsandbox/setup_script.ps1 From 80c7c28799397d1b817ed99b4969a2c7375a4480 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 18 Jun 2025 13:14:48 -0700 Subject: [PATCH 068/141] Implement interfaces, reorganize imports --- libs/computer/typescript/package.json | 4 +- libs/computer/typescript/pnpm-lock.yaml | 27 + .../typescript/src/computer/defaults.ts | 36 -- .../computer/typescript/src/computer/index.ts | 46 +- .../typescript/src/computer/providers/base.ts | 17 +- .../src/computer/providers/cloud.ts | 131 +++- .../src/computer/providers/index.ts | 3 + .../typescript/src/computer/providers/lume.ts | 25 +- .../computer/typescript/src/computer/types.ts | 45 +- libs/computer/typescript/src/index.ts | 7 +- .../computer/typescript/src/interface/base.ts | 259 ++++++++ .../typescript/src/interface/factory.ts | 57 ++ .../typescript/src/interface/index.ts | 6 + .../typescript/src/interface/linux.ts | 14 + .../typescript/src/interface/macos.ts | 207 ++++++ .../typescript/src/interface/windows.ts | 14 + libs/computer/typescript/src/types.ts | 10 + .../typescript/tests/computer/cloud.test.ts | 14 + .../typescript/tests/computer/lume.test.ts | 594 ----------------- libs/computer/typescript/tests/index.test.ts | 14 - .../typescript/tests/lume_api.test.ts | 597 ------------------ 21 files changed, 764 insertions(+), 1363 deletions(-) delete mode 100644 libs/computer/typescript/src/computer/defaults.ts create mode 100644 libs/computer/typescript/src/computer/providers/index.ts create mode 100644 libs/computer/typescript/src/interface/base.ts create mode 100644 libs/computer/typescript/src/interface/factory.ts create mode 100644 libs/computer/typescript/src/interface/index.ts create mode 100644 libs/computer/typescript/src/interface/linux.ts create mode 100644 libs/computer/typescript/src/interface/macos.ts create mode 100644 libs/computer/typescript/src/interface/windows.ts create mode 100644 libs/computer/typescript/src/types.ts create mode 100644 libs/computer/typescript/tests/computer/cloud.test.ts delete mode 100644 libs/computer/typescript/tests/computer/lume.test.ts delete mode 100644 libs/computer/typescript/tests/index.test.ts delete mode 100644 libs/computer/typescript/tests/lume_api.test.ts diff --git a/libs/computer/typescript/package.json b/libs/computer/typescript/package.json index 5a5167d6..71141f22 100644 --- a/libs/computer/typescript/package.json +++ b/libs/computer/typescript/package.json @@ -39,11 +39,13 @@ }, "dependencies": { "pino": "^9.7.0", - "sharp": "^0.33.0" + "sharp": "^0.33.0", + "ws": "^8.18.0" }, "devDependencies": { "@biomejs/biome": "^1.9.4", "@types/node": "^22.15.17", + "@types/ws": "^8.18.1", "bumpp": "^10.1.0", "happy-dom": "^17.4.7", "tsdown": "^0.11.9", diff --git a/libs/computer/typescript/pnpm-lock.yaml b/libs/computer/typescript/pnpm-lock.yaml index a0b97258..2cd52963 100644 --- a/libs/computer/typescript/pnpm-lock.yaml +++ b/libs/computer/typescript/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: sharp: specifier: ^0.33.0 version: 0.33.5 + ws: + specifier: ^8.18.0 + version: 8.18.2 devDependencies: '@biomejs/biome': specifier: ^1.9.4 @@ -21,6 +24,9 @@ importers: '@types/node': specifier: ^22.15.17 version: 22.15.31 + '@types/ws': + specifier: ^8.18.1 + version: 8.18.1 bumpp: specifier: ^10.1.0 version: 10.1.1 @@ -595,6 +601,9 @@ packages: '@types/node@22.15.31': resolution: {integrity: sha512-jnVe5ULKl6tijxUhvQeNbQG/84fHfg+yMak02cT8QVhBx/F05rAVxCGBYYTh2EKz22D6JF5ktXuNwdx7b9iEGw==} + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@vitest/expect@3.2.3': resolution: {integrity: sha512-W2RH2TPWVHA1o7UmaFKISPvdicFJH+mjykctJFoAkUw+SPTJTGjUNdKscFBrqM7IPnCVu6zihtKYa7TkZS1dkQ==} @@ -1124,6 +1133,18 @@ packages: engines: {node: '>=8'} hasBin: true + ws@8.18.2: + resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + yaml@2.8.0: resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==} engines: {node: '>= 14.6'} @@ -1510,6 +1531,10 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/ws@8.18.1': + dependencies: + '@types/node': 22.15.31 + '@vitest/expect@3.2.3': dependencies: '@types/chai': 5.2.2 @@ -2090,4 +2115,6 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + ws@8.18.2: {} + yaml@2.8.0: {} diff --git a/libs/computer/typescript/src/computer/defaults.ts b/libs/computer/typescript/src/computer/defaults.ts deleted file mode 100644 index bb438019..00000000 --- a/libs/computer/typescript/src/computer/defaults.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { OSType, VMProviderType } from "./types"; -import type { BaseComputerConfig } from "./types"; - -/** - * Default configuration values for Computer - */ -export const DEFAULT_CONFIG: Partial = { - name: "", - osType: OSType.MACOS, - vmProvider: VMProviderType.CLOUD, - display: "1024x768", - memory: "8GB", - cpu: 4, - image: "macos-sequoia-cua:latest", - sharedDirectories: [], - useHostComputerServer: false, - telemetryEnabled: true, - port: 7777, - noVNCPort: 8006, - host: "localhost", - ephemeral: false, -}; - -/** - * Apply default values to a computer configuration - * @param config Partial configuration - * @returns Complete configuration with defaults applied - */ -export function applyDefaults( - config: Partial -): T { - return { - ...DEFAULT_CONFIG, - ...config, - } as T; -} diff --git a/libs/computer/typescript/src/computer/index.ts b/libs/computer/typescript/src/computer/index.ts index c6501610..365dd478 100644 --- a/libs/computer/typescript/src/computer/index.ts +++ b/libs/computer/typescript/src/computer/index.ts @@ -1,45 +1 @@ -import type { BaseComputer } from "./providers/base"; -import { CloudComputer } from "./providers/cloud"; -import { LumeComputer } from "./providers/lume"; -import { - VMProviderType, - type BaseComputerConfig, - type CloudComputerConfig, - type LumeComputerConfig, -} from "./types"; -import { applyDefaults } from "./defaults"; -import pino from "pino"; - -export const logger = pino({ name: "computer" }); - -/** - * Factory class for creating the appropriate Computer instance - */ -export const Computer = { - /** - * Create a computer instance based on the provided configuration - * @param config The computer configuration - * @returns The appropriate computer instance based on the VM provider type - */ - create: ( - config: - | Partial - | Partial - | Partial - ): BaseComputer => { - // Apply defaults to the configuration - const fullConfig = applyDefaults(config); - - // Check the vmProvider property to determine which type of computer to create - switch (fullConfig.vmProvider) { - case VMProviderType.CLOUD: - return new CloudComputer(fullConfig as CloudComputerConfig); - case VMProviderType.LUME: - return new LumeComputer(fullConfig as LumeComputerConfig); - default: - throw new Error( - `Unsupported VM provider type: ${fullConfig.vmProvider}` - ); - } - }, -}; +export { BaseComputer, CloudComputer, LumeComputer } from "./providers"; diff --git a/libs/computer/typescript/src/computer/providers/base.ts b/libs/computer/typescript/src/computer/providers/base.ts index 3176709f..78cdcc8b 100644 --- a/libs/computer/typescript/src/computer/providers/base.ts +++ b/libs/computer/typescript/src/computer/providers/base.ts @@ -1,10 +1,8 @@ -import { logger } from "../index"; -import type { - BaseComputerConfig, - Display, - OSType, - VMProviderType, -} from "../types"; +import type { OSType } from "../../types"; +import type { BaseComputerConfig, Display, VMProviderType } from "../types"; +import pino from "pino"; + +const logger = pino({ name: "computer-base" }); /** * Base Computer class with shared functionality @@ -12,12 +10,11 @@ import type { export abstract class BaseComputer { protected name: string; protected osType: OSType; - protected vmProvider: VMProviderType; + protected vmProvider?: VMProviderType; constructor(config: BaseComputerConfig) { this.name = config.name; this.osType = config.osType; - this.vmProvider = config.vmProvider; } /** @@ -37,7 +34,7 @@ export abstract class BaseComputer { /** * Get the VM provider type */ - getVMProviderType(): VMProviderType { + getVMProviderType(): VMProviderType | undefined { return this.vmProvider; } diff --git a/libs/computer/typescript/src/computer/providers/cloud.ts b/libs/computer/typescript/src/computer/providers/cloud.ts index 612dc7f4..eb1ec1b1 100644 --- a/libs/computer/typescript/src/computer/providers/cloud.ts +++ b/libs/computer/typescript/src/computer/providers/cloud.ts @@ -1,31 +1,138 @@ import { BaseComputer } from "./base"; -import type { CloudComputerConfig } from "../types"; +import type { CloudComputerConfig, VMProviderType } from "../types"; +import { + InterfaceFactory, + type BaseComputerInterface, +} from "../../interface/index"; import pino from "pino"; -const logger = pino({ name: "cloud" }); +const logger = pino({ name: "computer-cloud" }); /** * Cloud-specific computer implementation */ export class CloudComputer extends BaseComputer { - private apiKey: string; + protected apiKey: string; + protected static vmProviderType: VMProviderType.CLOUD; + private interface?: BaseComputerInterface; + private initialized = false; + constructor(config: CloudComputerConfig) { super(config); this.apiKey = config.apiKey; } - /** - * Cloud-specific method to deploy the computer - */ - async deploy(): Promise { - logger.info(`Deploying cloud computer ${this.name}`); - // Cloud-specific implementation + get ip() { + return `${this.name}.containers.cloud.trycua.com`; } /** - * Cloud-specific method to get deployment status + * Initialize the cloud VM and interface */ - async getDeploymentStatus(): Promise { - return "running"; // Example implementation + async run(): Promise { + if (this.initialized) { + logger.info("Computer already initialized, skipping initialization"); + return; + } + + logger.info("Starting cloud computer..."); + + try { + // For cloud provider, the VM is already running, we just need to connect + const ipAddress = this.ip; + logger.info(`Connecting to cloud VM at ${ipAddress}`); + + // Create the interface with API key authentication + this.interface = InterfaceFactory.createInterfaceForOS( + this.osType, + ipAddress, + this.apiKey, + this.name + ); + + // Wait for the interface to be ready + logger.info("Waiting for interface to be ready..."); + await this.interface.waitForReady(); + + this.initialized = true; + logger.info("Cloud computer ready"); + } catch (error) { + logger.error(`Failed to initialize cloud computer: ${error}`); + throw new Error(`Failed to initialize cloud computer: ${error}`); + } + } + + /** + * Stop the cloud computer (disconnect interface) + */ + async stop(): Promise { + logger.info("Stopping cloud computer..."); + + if (this.interface) { + this.interface.close(); + this.interface = undefined; + } + + this.initialized = false; + logger.info("Cloud computer stopped"); + } + + /** + * Get the computer interface + */ + getInterface(): BaseComputerInterface { + if (!this.interface) { + throw new Error("Computer not initialized. Call run() first."); + } + return this.interface; + } + + /** + * Take a screenshot + */ + async screenshot(): Promise { + return this.getInterface().screenshot(); + } + + /** + * Click at coordinates + */ + async click(x?: number, y?: number): Promise { + return this.getInterface().leftClick(x, y); + } + + /** + * Type text + */ + async type(text: string): Promise { + return this.getInterface().typeText(text); + } + + /** + * Press a key + */ + async key(key: string): Promise { + return this.getInterface().pressKey(key); + } + + /** + * Press hotkey combination + */ + async hotkey(...keys: string[]): Promise { + return this.getInterface().hotkey(...keys); + } + + /** + * Run a command + */ + async runCommand(command: string): Promise<[string, string]> { + return this.getInterface().runCommand(command); + } + + /** + * Disconnect from the cloud computer + */ + async disconnect(): Promise { + await this.stop(); } } diff --git a/libs/computer/typescript/src/computer/providers/index.ts b/libs/computer/typescript/src/computer/providers/index.ts new file mode 100644 index 00000000..70ff7ae8 --- /dev/null +++ b/libs/computer/typescript/src/computer/providers/index.ts @@ -0,0 +1,3 @@ +export * from "./base"; +export * from "./cloud"; +export * from "./lume"; diff --git a/libs/computer/typescript/src/computer/providers/lume.ts b/libs/computer/typescript/src/computer/providers/lume.ts index 6e04bad6..e96f9221 100644 --- a/libs/computer/typescript/src/computer/providers/lume.ts +++ b/libs/computer/typescript/src/computer/providers/lume.ts @@ -3,9 +3,8 @@ * It serves as a reference for how a provider might be implemented but should not be used in production. */ -import type { Display, LumeComputerConfig } from "../types"; -import { BaseComputer } from "./base"; -import { applyDefaults } from "../defaults"; +import type { Display, LumeComputerConfig } from "../types.ts"; +import { BaseComputer } from "./base.ts"; import { lumeApiGet, lumeApiRun, @@ -14,10 +13,10 @@ import { lumeApiDelete, lumeApiUpdate, type VMInfo, -} from "../../util/lume"; +} from "../../util/lume.ts"; import pino from "pino"; -const logger = pino({ name: "lume_computer" }); +const logger = pino({ name: "computer-lume" }); /** * Lume-specific computer implementation @@ -34,15 +33,13 @@ export class LumeComputer extends BaseComputer { constructor(config: LumeComputerConfig) { super(config); - const defaultConfig = applyDefaults(config); - - this.display = defaultConfig.display; - this.memory = defaultConfig.memory; - this.cpu = defaultConfig.cpu; - this.image = defaultConfig.image; - this.port = defaultConfig.port; - this.host = defaultConfig.host; - this.ephemeral = defaultConfig.ephemeral; + this.display = config.display ?? "1024x768"; + this.memory = config.memory ?? "8GB"; + this.cpu = config.cpu ?? 2; + this.image = config.image ?? "macos-sequoia-cua:latest"; + this.port = config.port ?? 7777; + this.host = config.host ?? "localhost"; + this.ephemeral = config.ephemeral ?? false; } /** diff --git a/libs/computer/typescript/src/computer/types.ts b/libs/computer/typescript/src/computer/types.ts index 27c287e8..dbf8c734 100644 --- a/libs/computer/typescript/src/computer/types.ts +++ b/libs/computer/typescript/src/computer/types.ts @@ -1,9 +1,9 @@ +import type { OSType, ScreenSize } from "../types"; + /** * Display configuration for the computer. */ -export interface Display { - width: number; - height: number; +export interface Display extends ScreenSize { scale_factor?: number; } @@ -22,13 +22,16 @@ export interface BaseComputerConfig { * @default "macos" */ osType: OSType; +} +export interface CloudComputerConfig extends BaseComputerConfig { /** - * The VM provider type to use (lume, cloud) - * @default VMProviderType.LUME + * Optional API key for cloud providers */ - vmProvider: VMProviderType; + apiKey: string; +} +export interface LumeComputerConfig extends BaseComputerConfig { /** * The display configuration. Can be: * - A Display object @@ -108,37 +111,7 @@ export interface BaseComputerConfig { experiments?: string[]; } -export interface CloudComputerConfig extends BaseComputerConfig { - /** - * Optional API key for cloud providers - */ - apiKey: string; - - /** - * Size of the cloud VM - */ - size: "small" | "medium" | "large"; - - /** - * The Cloud VM provider type - */ - vmProvider: VMProviderType.CLOUD; -} - -export interface LumeComputerConfig extends BaseComputerConfig { - /** - * The Lume VM provider type - */ - vmProvider: VMProviderType.LUME; -} - export enum VMProviderType { CLOUD = "cloud", LUME = "lume", } - -export enum OSType { - MACOS = "macos", - WINDOWS = "windows", - LINUX = "linux", -} diff --git a/libs/computer/typescript/src/index.ts b/libs/computer/typescript/src/index.ts index 7c3c7bb1..35ea7c0d 100644 --- a/libs/computer/typescript/src/index.ts +++ b/libs/computer/typescript/src/index.ts @@ -1,5 +1,4 @@ -// Export types -export * from "./computer/types"; - -// Expore classes +// Export classes export * from "./computer"; + +//todo: figure out what types to export and how to do that diff --git a/libs/computer/typescript/src/interface/base.ts b/libs/computer/typescript/src/interface/base.ts new file mode 100644 index 00000000..209c1f24 --- /dev/null +++ b/libs/computer/typescript/src/interface/base.ts @@ -0,0 +1,259 @@ +/** + * Base interface for computer control. + */ + +import type { ScreenSize } from "../types"; +import WebSocket from "ws"; +import pino from "pino"; + +export type MouseButton = "left" | "middle" | "right"; + +export interface CursorPosition { + x: number; + y: number; +} + +export interface AccessibilityNode { + role: string; + title?: string; + value?: string; + description?: string; + bounds?: { + x: number; + y: number; + width: number; + height: number; + }; + children?: AccessibilityNode[]; +} + +/** + * Base class for computer control interfaces. + */ +export abstract class BaseComputerInterface { + protected ipAddress: string; + protected username: string; + protected password: string; + protected apiKey?: string; + protected vmName?: string; + protected ws?: WebSocket; + protected closed = false; + protected commandLock: Promise = Promise.resolve(); + protected logger = pino({ name: "interface-base" }); + + constructor( + ipAddress: string, + username = "lume", + password = "lume", + apiKey?: string, + vmName?: string + ) { + this.ipAddress = ipAddress; + this.username = username; + this.password = password; + this.apiKey = apiKey; + this.vmName = vmName; + } + + /** + * Get the WebSocket URI for connection. + * Subclasses can override this to customize the URI. + */ + protected get wsUri(): string { + // Use secure WebSocket for cloud provider with API key + const protocol = this.apiKey ? "wss" : "ws"; + const port = this.apiKey ? "8443" : "8000"; + return `${protocol}://${this.ipAddress}:${port}/ws`; + } + + /** + * Wait for interface to be ready. + * @param timeout Maximum time to wait in seconds + * @throws Error if interface is not ready within timeout + */ + async waitForReady(timeout = 60): Promise { + const startTime = Date.now(); + + while (Date.now() - startTime < timeout * 1000) { + try { + await this.connect(); + return; + } catch (error) { + // Wait a bit before retrying + this.logger.error(`Error connecting to websocket: ${error}`); + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + } + + throw new Error(`Interface not ready after ${timeout} seconds`); + } + + /** + * Connect to the WebSocket server. + */ + protected async connect(): Promise { + if (this.ws && this.ws.readyState === WebSocket.OPEN) { + return; + } + + return new Promise((resolve, reject) => { + const headers: { [key: string]: string } = {}; + if (this.apiKey && this.vmName) { + headers["X-API-Key"] = this.apiKey; + headers["X-VM-Name"] = this.vmName; + } + + this.ws = new WebSocket(this.wsUri, { headers }); + + this.ws.on("open", () => { + resolve(); + }); + + this.ws.on("error", (error: Error) => { + reject(error); + }); + + this.ws.on("close", () => { + if (!this.closed) { + // Attempt to reconnect + setTimeout(() => this.connect(), 1000); + } + }); + }); + } + + /** + * Send a command to the WebSocket server. + */ + protected async sendCommand(command: { + action: string; + [key: string]: unknown; + }): Promise<{ [key: string]: unknown }> { + // Create a new promise for this specific command + const commandPromise = new Promise<{ [key: string]: unknown }>( + (resolve, reject) => { + // Chain it to the previous commands + const executeCommand = async (): Promise<{ + [key: string]: unknown; + }> => { + if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { + await this.connect(); + } + + return new Promise<{ [key: string]: unknown }>( + (innerResolve, innerReject) => { + const messageHandler = (data: WebSocket.RawData) => { + try { + const response = JSON.parse(data.toString()); + if (response.error) { + innerReject(new Error(response.error)); + } else { + innerResolve(response); + } + } catch (error) { + innerReject(error); + } + this.ws!.off("message", messageHandler); + }; + + this.ws!.on("message", messageHandler); + this.ws!.send(JSON.stringify(command)); + } + ); + }; + + // Add this command to the lock chain + this.commandLock = this.commandLock.then(() => + executeCommand().then(resolve, reject) + ); + } + ); + + return commandPromise; + } + + /** + * Close the interface connection. + */ + close(): void { + this.closed = true; + if (this.ws) { + this.ws.close(); + this.ws = undefined; + } + } + + /** + * Force close the interface connection. + * By default, this just calls close(), but subclasses can override + * to provide more forceful cleanup. + */ + forceClose(): void { + this.close(); + } + + // Mouse Actions + abstract mouseDown( + x?: number, + y?: number, + button?: MouseButton + ): Promise; + abstract mouseUp(x?: number, y?: number, button?: MouseButton): Promise; + abstract leftClick(x?: number, y?: number): Promise; + abstract rightClick(x?: number, y?: number): Promise; + abstract doubleClick(x?: number, y?: number): Promise; + abstract moveCursor(x: number, y: number): Promise; + abstract dragTo( + x: number, + y: number, + button?: MouseButton, + duration?: number + ): Promise; + abstract drag( + path: Array<[number, number]>, + button?: MouseButton, + duration?: number + ): Promise; + + // Keyboard Actions + abstract keyDown(key: string): Promise; + abstract keyUp(key: string): Promise; + abstract typeText(text: string): Promise; + abstract pressKey(key: string): Promise; + abstract hotkey(...keys: string[]): Promise; + + // Scrolling Actions + abstract scroll(x: number, y: number): Promise; + abstract scrollDown(clicks?: number): Promise; + abstract scrollUp(clicks?: number): Promise; + + // Screen Actions + abstract screenshot(): Promise; + abstract getScreenSize(): Promise; + abstract getCursorPosition(): Promise; + + // Clipboard Actions + abstract copyToClipboard(): Promise; + abstract setClipboard(text: string): Promise; + + // File System Actions + abstract fileExists(path: string): Promise; + abstract directoryExists(path: string): Promise; + abstract listDir(path: string): Promise; + abstract readText(path: string): Promise; + abstract writeText(path: string, content: string): Promise; + abstract readBytes(path: string): Promise; + abstract writeBytes(path: string, content: Buffer): Promise; + abstract deleteFile(path: string): Promise; + abstract createDir(path: string): Promise; + abstract deleteDir(path: string): Promise; + abstract runCommand(command: string): Promise<[string, string]>; + + // Accessibility Actions + abstract getAccessibilityTree(): Promise; + abstract toScreenCoordinates(x: number, y: number): Promise<[number, number]>; + abstract toScreenshotCoordinates( + x: number, + y: number + ): Promise<[number, number]>; +} diff --git a/libs/computer/typescript/src/interface/factory.ts b/libs/computer/typescript/src/interface/factory.ts new file mode 100644 index 00000000..9621d45f --- /dev/null +++ b/libs/computer/typescript/src/interface/factory.ts @@ -0,0 +1,57 @@ +/** + * Factory for creating computer interfaces. + */ + +import type { BaseComputerInterface } from "./base"; +import { MacOSComputerInterface } from "./macos"; +import { LinuxComputerInterface } from "./linux"; +import { WindowsComputerInterface } from "./windows"; +import type { OSType } from "../types"; + +export const InterfaceFactory = { + /** + * Create an interface for the specified OS. + * + * @param os Operating system type ('macos', 'linux', or 'windows') + * @param ipAddress IP address of the computer to control + * @param apiKey Optional API key for cloud authentication + * @param vmName Optional VM name for cloud authentication + * @returns The appropriate interface for the OS + * @throws Error if the OS type is not supported + */ + createInterfaceForOS( + os: OSType, + ipAddress: string, + apiKey?: string, + vmName?: string + ): BaseComputerInterface { + switch (os) { + case "macos": + return new MacOSComputerInterface( + ipAddress, + "lume", + "lume", + apiKey, + vmName + ); + case "linux": + return new LinuxComputerInterface( + ipAddress, + "lume", + "lume", + apiKey, + vmName + ); + case "windows": + return new WindowsComputerInterface( + ipAddress, + "lume", + "lume", + apiKey, + vmName + ); + default: + throw new Error(`Unsupported OS type: ${os}`); + } + }, +}; diff --git a/libs/computer/typescript/src/interface/index.ts b/libs/computer/typescript/src/interface/index.ts new file mode 100644 index 00000000..84141da8 --- /dev/null +++ b/libs/computer/typescript/src/interface/index.ts @@ -0,0 +1,6 @@ +export { BaseComputerInterface } from "./base"; +export type { MouseButton, CursorPosition, AccessibilityNode } from "./base"; +export { InterfaceFactory } from "./factory"; +export { MacOSComputerInterface } from "./macos"; +export { LinuxComputerInterface } from "./linux"; +export { WindowsComputerInterface } from "./windows"; diff --git a/libs/computer/typescript/src/interface/linux.ts b/libs/computer/typescript/src/interface/linux.ts new file mode 100644 index 00000000..45717588 --- /dev/null +++ b/libs/computer/typescript/src/interface/linux.ts @@ -0,0 +1,14 @@ +/** + * Linux computer interface implementation. + */ + +import { MacOSComputerInterface } from "./macos"; + +/** + * Linux interface implementation. + * Since the cloud provider uses the same WebSocket protocol for all OS types, + * we can reuse the macOS implementation. + */ +export class LinuxComputerInterface extends MacOSComputerInterface { + // Linux uses the same WebSocket interface as macOS for cloud provider +} diff --git a/libs/computer/typescript/src/interface/macos.ts b/libs/computer/typescript/src/interface/macos.ts new file mode 100644 index 00000000..51df1294 --- /dev/null +++ b/libs/computer/typescript/src/interface/macos.ts @@ -0,0 +1,207 @@ +/** + * macOS computer interface implementation. + */ + +import { BaseComputerInterface } from "./base"; +import type { MouseButton, CursorPosition, AccessibilityNode } from "./base"; +import type { ScreenSize } from "../types"; + +export class MacOSComputerInterface extends BaseComputerInterface { + // Mouse Actions + async mouseDown( + x?: number, + y?: number, + button: MouseButton = "left" + ): Promise { + await this.sendCommand({ action: "mouse_down", x, y, button }); + } + + async mouseUp( + x?: number, + y?: number, + button: MouseButton = "left" + ): Promise { + await this.sendCommand({ action: "mouse_up", x, y, button }); + } + + async leftClick(x?: number, y?: number): Promise { + await this.sendCommand({ action: "left_click", x, y }); + } + + async rightClick(x?: number, y?: number): Promise { + await this.sendCommand({ action: "right_click", x, y }); + } + + async doubleClick(x?: number, y?: number): Promise { + await this.sendCommand({ action: "double_click", x, y }); + } + + async moveCursor(x: number, y: number): Promise { + await this.sendCommand({ action: "move_cursor", x, y }); + } + + async dragTo( + x: number, + y: number, + button: MouseButton = "left", + duration = 0.5 + ): Promise { + await this.sendCommand({ action: "drag_to", x, y, button, duration }); + } + + async drag( + path: Array<[number, number]>, + button: MouseButton = "left", + duration = 0.5 + ): Promise { + await this.sendCommand({ action: "drag", path, button, duration }); + } + + // Keyboard Actions + async keyDown(key: string): Promise { + await this.sendCommand({ action: "key_down", key }); + } + + async keyUp(key: string): Promise { + await this.sendCommand({ action: "key_up", key }); + } + + async typeText(text: string): Promise { + await this.sendCommand({ action: "type_text", text }); + } + + async pressKey(key: string): Promise { + await this.sendCommand({ action: "press_key", key }); + } + + async hotkey(...keys: string[]): Promise { + await this.sendCommand({ action: "hotkey", keys }); + } + + // Scrolling Actions + async scroll(x: number, y: number): Promise { + await this.sendCommand({ action: "scroll", x, y }); + } + + async scrollDown(clicks = 1): Promise { + await this.sendCommand({ action: "scroll_down", clicks }); + } + + async scrollUp(clicks = 1): Promise { + await this.sendCommand({ action: "scroll_up", clicks }); + } + + // Screen Actions + async screenshot(): Promise { + const response = await this.sendCommand({ action: "screenshot" }); + return Buffer.from(response.data as string, "base64"); + } + + async getScreenSize(): Promise { + const response = await this.sendCommand({ action: "get_screen_size" }); + return response.data as ScreenSize; + } + + async getCursorPosition(): Promise { + const response = await this.sendCommand({ action: "get_cursor_position" }); + return response.data as CursorPosition; + } + + // Clipboard Actions + async copyToClipboard(): Promise { + const response = await this.sendCommand({ action: "copy_to_clipboard" }); + return response.data as string; + } + + async setClipboard(text: string): Promise { + await this.sendCommand({ action: "set_clipboard", text }); + } + + // File System Actions + async fileExists(path: string): Promise { + const response = await this.sendCommand({ action: "file_exists", path }); + return response.data as boolean; + } + + async directoryExists(path: string): Promise { + const response = await this.sendCommand({ + action: "directory_exists", + path, + }); + return response.data as boolean; + } + + async listDir(path: string): Promise { + const response = await this.sendCommand({ action: "list_dir", path }); + return response.data as string[]; + } + + async readText(path: string): Promise { + const response = await this.sendCommand({ action: "read_text", path }); + return response.data as string; + } + + async writeText(path: string, content: string): Promise { + await this.sendCommand({ action: "write_text", path, content }); + } + + async readBytes(path: string): Promise { + const response = await this.sendCommand({ action: "read_bytes", path }); + return Buffer.from(response.data as string, "base64"); + } + + async writeBytes(path: string, content: Buffer): Promise { + await this.sendCommand({ + action: "write_bytes", + path, + content: content.toString("base64"), + }); + } + + async deleteFile(path: string): Promise { + await this.sendCommand({ action: "delete_file", path }); + } + + async createDir(path: string): Promise { + await this.sendCommand({ action: "create_dir", path }); + } + + async deleteDir(path: string): Promise { + await this.sendCommand({ action: "delete_dir", path }); + } + + async runCommand(command: string): Promise<[string, string]> { + const response = await this.sendCommand({ action: "run_command", command }); + const data = response.data as { stdout: string; stderr: string }; + return [data.stdout, data.stderr]; + } + + // Accessibility Actions + async getAccessibilityTree(): Promise { + const response = await this.sendCommand({ + action: "get_accessibility_tree", + }); + return response.data as AccessibilityNode; + } + + async toScreenCoordinates(x: number, y: number): Promise<[number, number]> { + const response = await this.sendCommand({ + action: "to_screen_coordinates", + x, + y, + }); + return response.data as [number, number]; + } + + async toScreenshotCoordinates( + x: number, + y: number + ): Promise<[number, number]> { + const response = await this.sendCommand({ + action: "to_screenshot_coordinates", + x, + y, + }); + return response.data as [number, number]; + } +} diff --git a/libs/computer/typescript/src/interface/windows.ts b/libs/computer/typescript/src/interface/windows.ts new file mode 100644 index 00000000..8fee3147 --- /dev/null +++ b/libs/computer/typescript/src/interface/windows.ts @@ -0,0 +1,14 @@ +/** + * Windows computer interface implementation. + */ + +import { MacOSComputerInterface } from "./macos"; + +/** + * Windows interface implementation. + * Since the cloud provider uses the same WebSocket protocol for all OS types, + * we can reuse the macOS implementation. + */ +export class WindowsComputerInterface extends MacOSComputerInterface { + // Windows uses the same WebSocket interface as macOS for cloud provider +} diff --git a/libs/computer/typescript/src/types.ts b/libs/computer/typescript/src/types.ts new file mode 100644 index 00000000..b6851b41 --- /dev/null +++ b/libs/computer/typescript/src/types.ts @@ -0,0 +1,10 @@ +export enum OSType { + MACOS = "macos", + WINDOWS = "windows", + LINUX = "linux", +} + +export interface ScreenSize { + width: number; + height: number; +} diff --git a/libs/computer/typescript/tests/computer/cloud.test.ts b/libs/computer/typescript/tests/computer/cloud.test.ts new file mode 100644 index 00000000..78653c09 --- /dev/null +++ b/libs/computer/typescript/tests/computer/cloud.test.ts @@ -0,0 +1,14 @@ +import { describe, expect, it } from "vitest"; +import { OSType } from "../../src/types"; +import { CloudComputer } from "../../src/computer/providers/cloud"; + +describe("Computer Cloud", () => { + it("Should create computer instance", () => { + const cloud = new CloudComputer({ + apiKey: "asdf", + name: "s-linux-1234", + osType: OSType.LINUX, + }); + expect(cloud).toBeInstanceOf(CloudComputer); + }); +}); diff --git a/libs/computer/typescript/tests/computer/lume.test.ts b/libs/computer/typescript/tests/computer/lume.test.ts deleted file mode 100644 index 127e632a..00000000 --- a/libs/computer/typescript/tests/computer/lume.test.ts +++ /dev/null @@ -1,594 +0,0 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; -import { LumeComputer } from "../../src/computer/providers/lume"; -import { VMProviderType, OSType } from "../../src/computer/types"; -import type { LumeComputerConfig, Display } from "../../src/computer/types"; -import type { VMInfo } from "../../src/util/lume"; -import * as lumeApi from "../../src/util/lume"; - -// Mock the lume API module -vi.mock("../../src/util/lume", () => ({ - lumeApiGet: vi.fn(), - lumeApiRun: vi.fn(), - lumeApiStop: vi.fn(), - lumeApiUpdate: vi.fn(), - lumeApiPull: vi.fn(), - lumeApiDelete: vi.fn(), -})); - -// Mock pino logger -vi.mock("pino", () => ({ - default: () => ({ - info: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - warn: vi.fn(), - }), -})); - -describe("LumeComputer", () => { - const mockVMInfo: VMInfo = { - name: "test-vm", - status: "running", - diskSize: { allocated: 1024, total: 10240 }, - memorySize: 2048, - os: "macos", - display: "1920x1080", - locationName: "local", - cpuCount: 4, - ipAddress: "192.168.1.100", - vncUrl: "vnc://localhost:5900", - sharedDirectories: [], - }; - - const defaultConfig: LumeComputerConfig = { - name: "test-vm", - osType: OSType.MACOS, - vmProvider: VMProviderType.LUME, - display: "1920x1080", - memory: "8GB", - cpu: 4, - image: "macos-sequoia-cua:latest", - port: 7777, - host: "localhost", - ephemeral: false, - }; - - beforeEach(() => { - vi.clearAllMocks(); - }); - - afterEach(() => { - vi.restoreAllMocks(); - }); - - describe("constructor", () => { - it("should initialize with default config", () => { - const computer = new LumeComputer(defaultConfig); - expect(computer.getName()).toBe("test-vm"); - expect(computer.getOSType()).toBe(OSType.MACOS); - expect(computer.getVMProviderType()).toBe(VMProviderType.LUME); - }); - - it("should accept display as string", () => { - const config = { ...defaultConfig, display: "1024x768" }; - const computer = new LumeComputer(config); - expect(computer).toBeDefined(); - }); - - it("should accept display as Display object", () => { - const display: Display = { width: 1920, height: 1080, scale_factor: 2 }; - const config = { ...defaultConfig, display }; - const computer = new LumeComputer(config); - expect(computer).toBeDefined(); - }); - }); - - describe("getVm", () => { - it("should get VM info successfully", async () => { - vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]); - - const computer = new LumeComputer(defaultConfig); - const result = await computer.getVm("test-vm"); - - expect(lumeApi.lumeApiGet).toHaveBeenCalledWith( - "test-vm", - "localhost", - 7777, - undefined - ); - expect(result).toEqual(mockVMInfo); - }); - - it("should handle VM not found error", async () => { - vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([]); - - const computer = new LumeComputer(defaultConfig); - await expect(computer.getVm("test-vm")).rejects.toThrow("VM Not Found."); - }); - - it("should handle stopped VM state", async () => { - const stoppedVM = { - ...mockVMInfo, - status: "stopped", - ipAddress: undefined, - }; - vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([stoppedVM]); - - const computer = new LumeComputer(defaultConfig); - const result = await computer.getVm("test-vm"); - - expect(result.status).toBe("stopped"); - expect(result.name).toBe("test-vm"); - }); - - it("should handle VM without IP address", async () => { - const noIpVM = { ...mockVMInfo, ipAddress: undefined }; - vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([noIpVM]); - - const computer = new LumeComputer(defaultConfig); - const result = await computer.getVm("test-vm"); - - expect(result).toEqual(noIpVM); - }); - - it("should pass storage parameter", async () => { - vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]); - - const computer = new LumeComputer(defaultConfig); - await computer.getVm("test-vm", "/custom/storage"); - - expect(lumeApi.lumeApiGet).toHaveBeenCalledWith( - "test-vm", - "localhost", - 7777, - "/custom/storage" - ); - }); - }); - - describe("listVm", () => { - it("should list all VMs", async () => { - const vmList = [mockVMInfo, { ...mockVMInfo, name: "another-vm" }]; - vi.mocked(lumeApi.lumeApiGet).mockResolvedValue(vmList); - - const computer = new LumeComputer(defaultConfig); - const result = await computer.listVm(); - - expect(lumeApi.lumeApiGet).toHaveBeenCalledWith("", "localhost", 7777); - expect(result).toEqual(vmList); - }); - }); - - describe("runVm", () => { - it("should run VM when it already exists", async () => { - vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]); - vi.mocked(lumeApi.lumeApiRun).mockResolvedValue(mockVMInfo); - - const computer = new LumeComputer(defaultConfig); - const runOpts = { memory: "4GB" }; - const result = await computer.runVm( - "macos-sequoia-cua:latest", - "test-vm", - runOpts - ); - - expect(lumeApi.lumeApiGet).toHaveBeenCalledWith( - "test-vm", - "localhost", - 7777, - undefined - ); - expect(lumeApi.lumeApiRun).toHaveBeenCalledWith( - "test-vm", - "localhost", - 7777, - runOpts, - undefined - ); - expect(result).toEqual(mockVMInfo); - }); - - it("should pull and run VM when it doesn't exist", async () => { - vi.mocked(lumeApi.lumeApiGet).mockRejectedValueOnce( - new Error("VM not found") - ); - vi.mocked(lumeApi.lumeApiPull).mockResolvedValue(mockVMInfo); - vi.mocked(lumeApi.lumeApiRun).mockResolvedValue(mockVMInfo); - - const computer = new LumeComputer(defaultConfig); - const result = await computer.runVm( - "macos-sequoia-cua:latest", - "test-vm" - ); - - expect(lumeApi.lumeApiGet).toHaveBeenCalled(); - expect(lumeApi.lumeApiPull).toHaveBeenCalledWith( - "macos-sequoia-cua:latest", - "test-vm", - "localhost", - 7777, - undefined, - "ghcr.io", - "trycua" - ); - expect(lumeApi.lumeApiRun).toHaveBeenCalled(); - expect(result).toEqual(mockVMInfo); - }); - - it("should handle pull failure", async () => { - vi.mocked(lumeApi.lumeApiGet).mockRejectedValueOnce( - new Error("VM not found") - ); - vi.mocked(lumeApi.lumeApiPull).mockRejectedValue( - new Error("Pull failed") - ); - - const computer = new LumeComputer(defaultConfig); - await expect( - computer.runVm("macos-sequoia-cua:latest", "test-vm") - ).rejects.toThrow("Pull failed"); - }); - - it("should pass storage parameter", async () => { - vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]); - vi.mocked(lumeApi.lumeApiRun).mockResolvedValue(mockVMInfo); - - const computer = new LumeComputer(defaultConfig); - await computer.runVm( - "macos-sequoia-cua:latest", - "test-vm", - {}, - "/storage" - ); - - expect(lumeApi.lumeApiGet).toHaveBeenCalledWith( - "test-vm", - "localhost", - 7777, - "/storage" - ); - expect(lumeApi.lumeApiRun).toHaveBeenCalledWith( - "test-vm", - "localhost", - 7777, - {}, - "/storage" - ); - }); - }); - - describe("stopVm", () => { - it("should stop VM normally", async () => { - vi.mocked(lumeApi.lumeApiStop).mockResolvedValue(mockVMInfo); - - const computer = new LumeComputer(defaultConfig); - const result = await computer.stopVm("test-vm"); - - expect(lumeApi.lumeApiStop).toHaveBeenCalledWith( - "test-vm", - "localhost", - 7777, - undefined - ); - expect(result).toEqual(mockVMInfo); - }); - - it("should delete VM after stopping in ephemeral mode", async () => { - const stoppedVM = { ...mockVMInfo, status: "stopped" }; - vi.mocked(lumeApi.lumeApiStop).mockResolvedValue(stoppedVM); - vi.mocked(lumeApi.lumeApiDelete).mockResolvedValue(null); - - const ephemeralConfig = { ...defaultConfig, ephemeral: true }; - const computer = new LumeComputer(ephemeralConfig); - const result = await computer.stopVm("test-vm"); - - expect(lumeApi.lumeApiStop).toHaveBeenCalled(); - expect(lumeApi.lumeApiDelete).toHaveBeenCalledWith( - "test-vm", - "localhost", - 7777, - undefined, - false, - false - ); - expect(result).toMatchObject({ - ...stoppedVM, - deleted: true, - deleteResult: null, - }); - }); - - it("should handle delete failure in ephemeral mode", async () => { - vi.mocked(lumeApi.lumeApiStop).mockResolvedValue(mockVMInfo); - vi.mocked(lumeApi.lumeApiDelete).mockRejectedValue( - new Error("Delete failed") - ); - - const ephemeralConfig = { ...defaultConfig, ephemeral: true }; - const computer = new LumeComputer(ephemeralConfig); - - await expect(computer.stopVm("test-vm")).rejects.toThrow( - "Failed to delete ephemeral VM test-vm: Error: Failed to delete VM: Error: Delete failed" - ); - }); - }); - - describe("pullVm", () => { - it("should pull VM image successfully", async () => { - vi.mocked(lumeApi.lumeApiPull).mockResolvedValue(mockVMInfo); - - const computer = new LumeComputer(defaultConfig); - const result = await computer.pullVm("test-vm", "ubuntu:latest"); - - expect(lumeApi.lumeApiPull).toHaveBeenCalledWith( - "ubuntu:latest", - "test-vm", - "localhost", - 7777, - undefined, - "ghcr.io", - "trycua" - ); - expect(result).toEqual(mockVMInfo); - }); - - it("should throw error if image parameter is missing", async () => { - const computer = new LumeComputer(defaultConfig); - await expect(computer.pullVm("test-vm", "")).rejects.toThrow( - "Image parameter is required for pullVm" - ); - }); - - it("should use custom registry and organization", async () => { - vi.mocked(lumeApi.lumeApiPull).mockResolvedValue(mockVMInfo); - - const computer = new LumeComputer(defaultConfig); - await computer.pullVm( - "test-vm", - "custom:tag", - "/storage", - "docker.io", - "myorg" - ); - - expect(lumeApi.lumeApiPull).toHaveBeenCalledWith( - "custom:tag", - "test-vm", - "localhost", - 7777, - "/storage", - "docker.io", - "myorg" - ); - }); - - it("should handle pull failure", async () => { - vi.mocked(lumeApi.lumeApiPull).mockRejectedValue( - new Error("Network error") - ); - - const computer = new LumeComputer(defaultConfig); - await expect(computer.pullVm("test-vm", "ubuntu:latest")).rejects.toThrow( - "Failed to pull VM: Error: Network error" - ); - }); - }); - - describe("deleteVm", () => { - it("should delete VM successfully", async () => { - vi.mocked(lumeApi.lumeApiDelete).mockResolvedValue(null); - - const computer = new LumeComputer(defaultConfig); - const result = await computer.deleteVm("test-vm"); - - expect(lumeApi.lumeApiDelete).toHaveBeenCalledWith( - "test-vm", - "localhost", - 7777, - undefined, - false, - false - ); - expect(result).toBeNull(); - }); - - it("should handle delete failure", async () => { - vi.mocked(lumeApi.lumeApiDelete).mockRejectedValue( - new Error("Permission denied") - ); - - const computer = new LumeComputer(defaultConfig); - await expect(computer.deleteVm("test-vm")).rejects.toThrow( - "Failed to delete VM: Error: Permission denied" - ); - }); - - it("should pass storage parameter", async () => { - vi.mocked(lumeApi.lumeApiDelete).mockResolvedValue(null); - - const computer = new LumeComputer(defaultConfig); - await computer.deleteVm("test-vm", "/storage"); - - expect(lumeApi.lumeApiDelete).toHaveBeenCalledWith( - "test-vm", - "localhost", - 7777, - "/storage", - false, - false - ); - }); - }); - - describe("updateVm", () => { - it("should update VM configuration", async () => { - const updatedVM = { ...mockVMInfo, memorySize: 4096 }; - vi.mocked(lumeApi.lumeApiUpdate).mockResolvedValue(updatedVM); - - const computer = new LumeComputer(defaultConfig); - const updateOpts = { memory: "4GB" }; - const result = await computer.updateVm("test-vm", updateOpts); - - expect(lumeApi.lumeApiUpdate).toHaveBeenCalledWith( - "test-vm", - "localhost", - 7777, - updateOpts, - undefined, - false, - false - ); - expect(result).toEqual(updatedVM); - }); - - it("should pass storage parameter", async () => { - vi.mocked(lumeApi.lumeApiUpdate).mockResolvedValue(mockVMInfo); - - const computer = new LumeComputer(defaultConfig); - await computer.updateVm("test-vm", {}, "/storage"); - - expect(lumeApi.lumeApiUpdate).toHaveBeenCalledWith( - "test-vm", - "localhost", - 7777, - {}, - "/storage", - false, - false - ); - }); - }); - - describe("getIp", () => { - it("should return IP address immediately if available", async () => { - vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]); - - const computer = new LumeComputer(defaultConfig); - const ip = await computer.getIp("test-vm"); - - expect(ip).toBe("192.168.1.100"); - expect(lumeApi.lumeApiGet).toHaveBeenCalledTimes(1); - }); - - it("should retry until IP address is available", async () => { - const noIpVM = { ...mockVMInfo, ipAddress: undefined }; - vi.mocked(lumeApi.lumeApiGet) - .mockResolvedValueOnce([noIpVM]) - .mockResolvedValueOnce([noIpVM]) - .mockResolvedValueOnce([mockVMInfo]); - - const computer = new LumeComputer(defaultConfig); - const ip = await computer.getIp("test-vm", undefined, 0.1); // Short retry delay for testing - - expect(ip).toBe("192.168.1.100"); - expect(lumeApi.lumeApiGet).toHaveBeenCalledTimes(3); - }); - - it("should throw error if VM is stopped", async () => { - const stoppedVM = { - ...mockVMInfo, - status: "stopped", - ipAddress: undefined, - }; - vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([stoppedVM]); - - const computer = new LumeComputer(defaultConfig); - await expect(computer.getIp("test-vm")).rejects.toThrow( - "VM test-vm is in 'stopped' state and will not get an IP address" - ); - }); - - it("should throw error if VM is in error state", async () => { - const errorVM = { ...mockVMInfo, status: "error", ipAddress: undefined }; - vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([errorVM]); - - const computer = new LumeComputer(defaultConfig); - await expect(computer.getIp("test-vm")).rejects.toThrow( - "VM test-vm is in 'error' state and will not get an IP address" - ); - }); - - it("should handle getVm errors", async () => { - vi.mocked(lumeApi.lumeApiGet).mockRejectedValue( - new Error("Network error") - ); - - const computer = new LumeComputer(defaultConfig); - await expect(computer.getIp("test-vm")).rejects.toThrow("Network error"); - }); - - it("should pass storage parameter", async () => { - vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]); - - const computer = new LumeComputer(defaultConfig); - await computer.getIp("test-vm", "/storage"); - - expect(lumeApi.lumeApiGet).toHaveBeenCalledWith( - "test-vm", - "localhost", - 7777, - "/storage" - ); - }); - }); - - describe("integration scenarios", () => { - it("should handle full VM lifecycle", async () => { - // Simulate VM not existing initially - vi.mocked(lumeApi.lumeApiGet).mockRejectedValueOnce( - new Error("VM not found") - ); - vi.mocked(lumeApi.lumeApiPull).mockResolvedValue(mockVMInfo); - - // Simulate VM starting without IP, then getting IP - const startingVM = { - ...mockVMInfo, - ipAddress: undefined, - status: "starting", - }; - vi.mocked(lumeApi.lumeApiRun).mockResolvedValue(startingVM); - vi.mocked(lumeApi.lumeApiGet) - .mockResolvedValueOnce([startingVM]) - .mockResolvedValueOnce([mockVMInfo]); - - // Simulate stop - const stoppedVM = { ...mockVMInfo, status: "stopped" }; - vi.mocked(lumeApi.lumeApiStop).mockResolvedValue(stoppedVM); - - const computer = new LumeComputer(defaultConfig); - - // Run VM (should pull first) - await computer.runVm("macos-sequoia-cua:latest", "test-vm"); - - // Get IP (should retry once) - const ip = await computer.getIp("test-vm", undefined, 0.1); - expect(ip).toBe("192.168.1.100"); - - // Stop VM - const stopResult = await computer.stopVm("test-vm"); - expect(stopResult.status).toBe("stopped"); - }); - - it("should handle ephemeral VM lifecycle", async () => { - const ephemeralConfig = { ...defaultConfig, ephemeral: true }; - const computer = new LumeComputer(ephemeralConfig); - - // Setup mocks - vi.mocked(lumeApi.lumeApiGet).mockResolvedValue([mockVMInfo]); - vi.mocked(lumeApi.lumeApiRun).mockResolvedValue(mockVMInfo); - vi.mocked(lumeApi.lumeApiStop).mockResolvedValue({ - ...mockVMInfo, - status: "stopped", - }); - vi.mocked(lumeApi.lumeApiDelete).mockResolvedValue(null); - - // Run and stop ephemeral VM - await computer.runVm("macos-sequoia-cua:latest", "test-vm"); - const result = await computer.stopVm("test-vm"); - - // Verify VM was deleted - expect(lumeApi.lumeApiDelete).toHaveBeenCalled(); - expect(result).toBe(null); - }); - }); -}); diff --git a/libs/computer/typescript/tests/index.test.ts b/libs/computer/typescript/tests/index.test.ts deleted file mode 100644 index 5d866ba4..00000000 --- a/libs/computer/typescript/tests/index.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { describe, it } from "vitest"; -import { Computer, OSType, VMProviderType } from "../src/index"; - -describe("Cloud Interface", () => { - it("should create a cloud computer", () => { - const computer = Computer.create({ - vmProvider: VMProviderType.CLOUD, - name: "computer-name", - size: "small", - osType: OSType.LINUX, - apiKey: "asdf", - }); - }); -}); diff --git a/libs/computer/typescript/tests/lume_api.test.ts b/libs/computer/typescript/tests/lume_api.test.ts deleted file mode 100644 index e6b28802..00000000 --- a/libs/computer/typescript/tests/lume_api.test.ts +++ /dev/null @@ -1,597 +0,0 @@ -import { describe, expect, it, vi } from "vitest"; -import { - lumeApiGet, - lumeApiRun, - lumeApiStop, - lumeApiUpdate, - lumeApiPull, - lumeApiDelete, - type VMInfo, - type RunOptions, - type UpdateOptions, -} from "../src/util/lume"; - -const PORT = 1213; -const HOST = "localhost"; - -describe("Lume API", () => { - describe("lumeApiGet", () => { - it("should fetch VM information successfully", async () => { - // Mock fetch for this test - API returns a single VMDetails object - const mockVMInfo: VMInfo = { - name: "test-vm", - status: "stopped", - diskSize: { allocated: 1024, total: 10240 }, - memorySize: 2048, - os: "ubuntu", - display: "1920x1080", - locationName: "local", - cpuCount: 2, - sharedDirectories: [ - { - hostPath: "/home/user/shared", - tag: "shared", - readOnly: false, - }, - ], - }; - - global.fetch = vi.fn().mockResolvedValueOnce({ - ok: true, - json: async () => mockVMInfo, - headers: new Headers({ "content-type": "application/json" }), - } as Response); - - const result = await lumeApiGet("test-vm", HOST, PORT); - - expect(result).toEqual([mockVMInfo]); - expect(fetch).toHaveBeenCalledWith( - `http://${HOST}:${PORT}/lume/vms/test-vm`, - expect.objectContaining({ - method: "GET", - signal: expect.any(AbortSignal), - }) - ); - }); - - it("should list all VMs when name is empty", async () => { - // Mock fetch for list VMs - API returns an array - const mockVMList: VMInfo[] = [ - { - name: "vm1", - status: "running", - diskSize: { allocated: 1024, total: 10240 }, - memorySize: 2048, - os: "ubuntu", - display: "1920x1080", - locationName: "local", - cpuCount: 2, - }, - { - name: "vm2", - status: "stopped", - diskSize: { allocated: 2048, total: 10240 }, - memorySize: 4096, - os: "debian", - display: "1920x1080", - locationName: "local", - cpuCount: 4, - }, - ]; - - global.fetch = vi.fn().mockResolvedValueOnce({ - ok: true, - json: async () => mockVMList, - headers: new Headers({ "content-type": "application/json" }), - } as Response); - - const result = await lumeApiGet("", HOST, PORT); - - expect(result).toEqual(mockVMList); - expect(fetch).toHaveBeenCalledWith( - `http://${HOST}:${PORT}/lume/vms`, - expect.objectContaining({ - method: "GET", - signal: expect.any(AbortSignal), - }) - ); - }); - - it("should handle storage parameter encoding correctly", async () => { - const mockVMInfo: VMInfo = { - name: "test-vm", - status: "stopped", - diskSize: { allocated: 1024, total: 10240 }, - memorySize: 2048, - os: "ubuntu", - display: "1920x1080", - locationName: "local", - cpuCount: 2, - }; - - global.fetch = vi.fn().mockResolvedValueOnce({ - ok: true, - json: async () => mockVMInfo, - headers: new Headers({ "content-type": "application/json" }), - } as Response); - - const storage = "/path/with spaces/and#special?chars"; - await lumeApiGet("test-vm", HOST, PORT, storage); - - const expectedUrl = `http://${HOST}:${PORT}/lume/vms/test-vm?storage=${encodeURIComponent( - storage - )}`; - expect(fetch).toHaveBeenCalledWith( - expectedUrl, - expect.objectContaining({ - method: "GET", - }) - ); - }); - - it("should handle HTTP errors", async () => { - global.fetch = vi.fn().mockResolvedValueOnce({ - ok: false, - status: 404, - headers: new Headers(), - } as Response); - - await expect(lumeApiGet("test-vm", HOST, PORT)).rejects.toThrow( - "API request failed: HTTP error returned from API server (status: 404)" - ); - }); - - it("should handle connection refused errors", async () => { - const error = new Error("Connection refused"); - (error as Error).message = "ECONNREFUSED"; - global.fetch = vi.fn().mockRejectedValueOnce(error); - - await expect(lumeApiGet("test-vm", HOST, PORT)).rejects.toThrow( - "API request failed: Failed to connect to the API server - it might still be starting up" - ); - }); - - it("should handle timeout errors", async () => { - const error = new Error("Request aborted"); - error.name = "AbortError"; - global.fetch = vi.fn().mockRejectedValueOnce(error); - - await expect(lumeApiGet("test-vm", HOST, PORT)).rejects.toThrow( - "API request failed: Operation timeout - the API server is taking too long to respond" - ); - }); - - it("should handle host not found errors", async () => { - const error = new Error("Host not found"); - (error as Error).message = "ENOTFOUND"; - global.fetch = vi.fn().mockRejectedValueOnce(error); - - await expect(lumeApiGet("test-vm", HOST, PORT)).rejects.toThrow( - "API request failed: Failed to resolve host - check the API server address" - ); - }); - }); - - describe("lumeApiRun", () => { - it("should run a VM successfully", async () => { - const mockVMInfo: VMInfo = { - name: "test-vm", - status: "running", - diskSize: { allocated: 1024, total: 10240 }, - memorySize: 2048, - os: "ubuntu", - display: "1920x1080", - locationName: "local", - cpuCount: 2, - vncUrl: "vnc://localhost:5900", - ipAddress: "192.168.1.100", - }; - - global.fetch = vi.fn().mockResolvedValueOnce({ - ok: true, - json: async () => mockVMInfo, - headers: new Headers({ "content-type": "application/json" }), - } as Response); - - const runOpts: RunOptions = { - memory: "2G", - cpus: 2, - display: "1920x1080", - }; - - const result = await lumeApiRun("test-vm", HOST, PORT, runOpts); - - expect(result).toEqual(mockVMInfo); - expect(fetch).toHaveBeenCalledWith( - `http://${HOST}:${PORT}/lume/vms/test-vm/run`, - expect.objectContaining({ - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(runOpts), - signal: expect.any(AbortSignal), - }) - ); - }); - - it("should handle storage parameter in run request", async () => { - global.fetch = vi.fn().mockResolvedValueOnce({ - ok: true, - json: async () => ({ name: "test-vm" }), - headers: new Headers({ "content-type": "application/json" }), - } as Response); - - const storage = "/custom/storage/path"; - const runOpts: RunOptions = { memory: "1G" }; - - await lumeApiRun("test-vm", HOST, PORT, runOpts, storage); - - const expectedUrl = `http://${HOST}:${PORT}/lume/vms/test-vm/run?storage=${encodeURIComponent( - storage - )}`; - expect(fetch).toHaveBeenCalledWith( - expectedUrl, - expect.objectContaining({ - method: "POST", - }) - ); - }); - - it("should handle run errors", async () => { - global.fetch = vi.fn().mockResolvedValueOnce({ - ok: false, - status: 500, - headers: new Headers(), - } as Response); - - await expect(lumeApiRun("test-vm", HOST, PORT, {})).rejects.toThrow( - "API request failed: HTTP error returned from API server (status: 500)" - ); - }); - }); - - describe("lumeApiStop", () => { - it("should stop a VM successfully", async () => { - const mockVMInfo: VMInfo = { - name: "test-vm", - status: "stopped", - diskSize: { allocated: 1024, total: 10240 }, - memorySize: 2048, - os: "ubuntu", - display: "1920x1080", - locationName: "local", - cpuCount: 2, - }; - - global.fetch = vi.fn().mockResolvedValueOnce({ - ok: true, - json: async () => mockVMInfo, - headers: new Headers({ "content-type": "application/json" }), - } as Response); - - const result = await lumeApiStop("test-vm", HOST, PORT); - - expect(result).toEqual(mockVMInfo); - expect(fetch).toHaveBeenCalledWith( - `http://${HOST}:${PORT}/lume/vms/test-vm/stop`, - expect.objectContaining({ - method: "POST", - signal: expect.any(AbortSignal), - headers: { - "Content-Type": "application/json", - }, - body: "{}", - }) - ); - }); - - it("should handle storage parameter in stop request", async () => { - global.fetch = vi.fn().mockResolvedValueOnce({ - ok: true, - json: async () => ({ name: "test-vm" }), - headers: new Headers({ "content-type": "application/json" }), - } as Response); - - const storage = "/storage/path"; - await lumeApiStop("test-vm", HOST, PORT, storage); - - const expectedUrl = `http://${HOST}:${PORT}/lume/vms/test-vm/stop?storage=${encodeURIComponent( - storage - )}`; - expect(fetch).toHaveBeenCalledWith( - expectedUrl, - expect.objectContaining({ - method: "POST", - }) - ); - }); - }); - - describe("lumeApiUpdate", () => { - it("should update VM settings successfully", async () => { - const mockVMInfo: VMInfo = { - name: "test-vm", - status: "stopped", - diskSize: { allocated: 1024, total: 10240 }, - memorySize: 4096, - os: "ubuntu", - display: "2560x1440", - locationName: "local", - cpuCount: 2, - }; - - global.fetch = vi.fn().mockResolvedValueOnce({ - ok: true, - json: async () => mockVMInfo, - headers: new Headers({ "content-type": "application/json" }), - } as Response); - - const updateOpts: UpdateOptions = { - memory: "4G", - display: "2560x1440", - }; - - const result = await lumeApiUpdate("test-vm", HOST, PORT, updateOpts); - - expect(result).toEqual(mockVMInfo); - expect(fetch).toHaveBeenCalledWith( - `http://${HOST}:${PORT}/lume/vms/test-vm/update`, - expect.objectContaining({ - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(updateOpts), - signal: expect.any(AbortSignal), - }) - ); - }); - - it("should handle empty update options", async () => { - global.fetch = vi.fn().mockResolvedValueOnce({ - ok: true, - json: async () => ({ name: "test-vm" }), - headers: new Headers({ "content-type": "application/json" }), - } as Response); - - await lumeApiUpdate("test-vm", HOST, PORT, {}); - - expect(fetch).toHaveBeenCalledWith( - expect.any(String), - expect.objectContaining({ - body: "{}", - }) - ); - }); - }); - - describe("lumeApiPull", () => { - it("should pull a VM image successfully", async () => { - const mockVMInfo: VMInfo = { - name: "pulled-vm", - status: "stopped", - diskSize: { allocated: 2048, total: 10240 }, - memorySize: 2048, - os: "ubuntu", - display: "1920x1080", - locationName: "local", - cpuCount: 2, - }; - - global.fetch = vi.fn().mockResolvedValueOnce({ - ok: true, - json: async () => mockVMInfo, - headers: new Headers({ "content-type": "application/json" }), - } as Response); - - const result = await lumeApiPull( - "ubuntu:latest", - "pulled-vm", - HOST, - PORT - ); - - expect(result).toEqual(mockVMInfo); - expect(fetch).toHaveBeenCalledWith( - `http://${HOST}:${PORT}/lume/pull`, - expect.objectContaining({ - method: "POST", - signal: expect.any(AbortSignal), - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - image: "ubuntu:latest", - name: "pulled-vm", - registry: "ghcr.io", - organization: "trycua", - }), - }) - ); - }); - - it("should use custom registry and organization", async () => { - global.fetch = vi.fn().mockResolvedValueOnce({ - ok: true, - json: async () => ({ name: "custom-vm" }), - headers: new Headers({ "content-type": "application/json" }), - } as Response); - - await lumeApiPull( - "custom:tag", - "custom-vm", - HOST, - PORT, - undefined, - "docker.io", - "myorg" - ); - - expect(fetch).toHaveBeenCalledWith( - expect.any(String), - expect.objectContaining({ - body: JSON.stringify({ - image: "custom:tag", - name: "custom-vm", - registry: "docker.io", - organization: "myorg", - }), - }) - ); - }); - - it("should handle storage parameter in pull request", async () => { - global.fetch = vi.fn().mockResolvedValueOnce({ - ok: true, - json: async () => ({ name: "test-vm" }), - headers: new Headers({ "content-type": "application/json" }), - } as Response); - - const storage = "/custom/storage"; - await lumeApiPull("image:tag", "test-vm", HOST, PORT, storage); - - const expectedUrl = `http://${HOST}:${PORT}/lume/pull?storage=${encodeURIComponent( - storage - )}`; - expect(fetch).toHaveBeenCalledWith( - expectedUrl, - expect.objectContaining({ - method: "POST", - }) - ); - }); - }); - - describe("lumeApiDelete", () => { - it("should delete a VM successfully", async () => { - const mockVMInfo: VMInfo = { - name: "test-vm", - status: "deleted", - diskSize: { allocated: 0, total: 0 }, - memorySize: 0, - os: "ubuntu", - display: "", - locationName: "local", - cpuCount: 0, - }; - - global.fetch = vi.fn().mockResolvedValueOnce({ - ok: true, - json: async () => mockVMInfo, - headers: new Headers({ "content-type": "application/json" }), - } as Response); - - const result = await lumeApiDelete("test-vm", HOST, PORT); - - expect(result).toEqual(mockVMInfo); - expect(fetch).toHaveBeenCalledWith( - `http://${HOST}:${PORT}/lume/vms/test-vm`, - expect.objectContaining({ - method: "DELETE", - signal: expect.any(AbortSignal), - }) - ); - }); - - it("should handle storage parameter in delete request", async () => { - const storage = "/custom/storage"; - global.fetch = vi.fn().mockResolvedValueOnce({ - ok: true, - json: async () => null, - headers: new Headers(), - } as Response); - - await lumeApiDelete("test-vm", HOST, PORT, storage); - - const expectedUrl = `http://${HOST}:${PORT}/lume/vms/test-vm?storage=${encodeURIComponent( - storage - )}`; - expect(fetch).toHaveBeenCalledWith( - expectedUrl, - expect.objectContaining({ - method: "DELETE", - }) - ); - }); - - it("should handle 404 as successful deletion", async () => { - global.fetch = vi.fn().mockResolvedValueOnce({ - ok: false, - status: 404, - headers: new Headers(), - } as Response); - - const result = await lumeApiDelete("non-existent-vm", HOST, PORT); - - expect(result).toBeNull(); - }); - - it("should throw error for non-404 HTTP errors", async () => { - global.fetch = vi.fn().mockResolvedValueOnce({ - ok: false, - status: 500, - headers: new Headers(), - } as Response); - - await expect(lumeApiDelete("test-vm", HOST, PORT)).rejects.toThrow( - "API request failed: HTTP error returned from API server (status: 500)" - ); - }); - }); - - describe("Debug and Verbose Logging", () => { - it("should log debug information when debug is true", async () => { - const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {}); - - global.fetch = vi.fn().mockResolvedValueOnce({ - ok: true, - json: async () => [{ name: "test-vm", cpuCount: 2 }], - headers: new Headers({ "content-type": "application/json" }), - } as Response); - - await lumeApiGet("", HOST, PORT, undefined, true); // Empty name for list - - expect(consoleSpy).toHaveBeenCalledWith( - expect.stringContaining("DEBUG: API response:") - ); - - consoleSpy.mockRestore(); - }); - - it("should log verbose information when verbose is true", async () => { - const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {}); - - global.fetch = vi.fn().mockResolvedValueOnce({ - ok: true, - json: async () => ({ name: "test-vm", cpuCount: 2 }), - headers: new Headers({ "content-type": "application/json" }), - } as Response); - - await lumeApiRun( - "test-vm", - HOST, - PORT, - { memory: "1G" }, - undefined, - false, - true - ); - - expect(consoleSpy).toHaveBeenCalled(); - - consoleSpy.mockRestore(); - }); - }); - - describe("Error Message Handling", () => { - it("should handle generic errors with message", async () => { - const error = new Error("Custom error message"); - global.fetch = vi.fn().mockRejectedValueOnce(error); - - await expect(lumeApiGet("test-vm", HOST, PORT)).rejects.toThrow( - "API request failed: Custom error message" - ); - }); - }); -}); From 10c0fecc1acd7b1d466cbc19395d86e9885cbd72 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 18 Jun 2025 13:19:25 -0700 Subject: [PATCH 069/141] Websocket always defined --- .../computer/typescript/src/interface/base.ts | 41 +++++++++++++++---- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/libs/computer/typescript/src/interface/base.ts b/libs/computer/typescript/src/interface/base.ts index 209c1f24..aef1e2be 100644 --- a/libs/computer/typescript/src/interface/base.ts +++ b/libs/computer/typescript/src/interface/base.ts @@ -34,11 +34,12 @@ export abstract class BaseComputerInterface { protected ipAddress: string; protected username: string; protected password: string; - protected apiKey?: string; - protected vmName?: string; - protected ws?: WebSocket; protected closed = false; protected commandLock: Promise = Promise.resolve(); + protected ws: WebSocket; + protected apiKey?: string; + protected vmName?: string; + protected logger = pino({ name: "interface-base" }); constructor( @@ -53,6 +54,16 @@ export abstract class BaseComputerInterface { this.password = password; this.apiKey = apiKey; this.vmName = vmName; + + // Initialize WebSocket with headers if needed + const headers: { [key: string]: string } = {}; + if (this.apiKey && this.vmName) { + headers["X-API-Key"] = this.apiKey; + headers["X-VM-Name"] = this.vmName; + } + + // Create the WebSocket instance + this.ws = new WebSocket(this.wsUri, { headers }); } /** @@ -92,19 +103,34 @@ export abstract class BaseComputerInterface { * Connect to the WebSocket server. */ protected async connect(): Promise { - if (this.ws && this.ws.readyState === WebSocket.OPEN) { + if (this.ws.readyState === WebSocket.OPEN) { return; } - return new Promise((resolve, reject) => { + // If the WebSocket is closed or closing, reinitialize it + if ( + this.ws.readyState === WebSocket.CLOSED || + this.ws.readyState === WebSocket.CLOSING + ) { const headers: { [key: string]: string } = {}; if (this.apiKey && this.vmName) { headers["X-API-Key"] = this.apiKey; headers["X-VM-Name"] = this.vmName; } - this.ws = new WebSocket(this.wsUri, { headers }); + } + return new Promise((resolve, reject) => { + // If already connecting, wait for it to complete + if (this.ws.readyState === WebSocket.CONNECTING) { + this.ws.addEventListener("open", () => resolve(), { once: true }); + this.ws.addEventListener("error", (error) => reject(error), { + once: true, + }); + return; + } + + // Set up event handlers this.ws.on("open", () => { resolve(); }); @@ -177,9 +203,8 @@ export abstract class BaseComputerInterface { */ close(): void { this.closed = true; - if (this.ws) { + if (this.ws && this.ws.readyState !== WebSocket.CLOSED) { this.ws.close(); - this.ws = undefined; } } From 593afe220f972e706034fac7dea2ee70f5015509 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 18 Jun 2025 15:26:07 -0700 Subject: [PATCH 070/141] Create tests. Note: ws mocking is currently broken --- libs/computer/typescript/package.json | 1 + libs/computer/typescript/pnpm-lock.yaml | 418 +++++++++++++++- .../src/computer/providers/cloud.ts | 2 +- .../computer/typescript/src/interface/base.ts | 24 +- .../tests/interface/factory.test.ts | 71 +++ .../typescript/tests/interface/index.test.ts | 40 ++ .../tests/interface/integration.test.ts | 447 ++++++++++++++++++ .../typescript/tests/interface/linux.test.ts | 28 ++ .../typescript/tests/interface/macos.test.ts | 444 +++++++++++++++++ .../tests/interface/windows.test.ts | 28 ++ libs/computer/typescript/tests/setup.ts | 26 + libs/computer/typescript/vitest.config.ts | 10 +- 12 files changed, 1526 insertions(+), 13 deletions(-) create mode 100644 libs/computer/typescript/tests/interface/factory.test.ts create mode 100644 libs/computer/typescript/tests/interface/index.test.ts create mode 100644 libs/computer/typescript/tests/interface/integration.test.ts create mode 100644 libs/computer/typescript/tests/interface/linux.test.ts create mode 100644 libs/computer/typescript/tests/interface/macos.test.ts create mode 100644 libs/computer/typescript/tests/interface/windows.test.ts create mode 100644 libs/computer/typescript/tests/setup.ts diff --git a/libs/computer/typescript/package.json b/libs/computer/typescript/package.json index 71141f22..8ff5c095 100644 --- a/libs/computer/typescript/package.json +++ b/libs/computer/typescript/package.json @@ -48,6 +48,7 @@ "@types/ws": "^8.18.1", "bumpp": "^10.1.0", "happy-dom": "^17.4.7", + "msw": "^2.10.2", "tsdown": "^0.11.9", "tsx": "^4.19.4", "typescript": "^5.8.3", diff --git a/libs/computer/typescript/pnpm-lock.yaml b/libs/computer/typescript/pnpm-lock.yaml index 2cd52963..1abeebdc 100644 --- a/libs/computer/typescript/pnpm-lock.yaml +++ b/libs/computer/typescript/pnpm-lock.yaml @@ -33,6 +33,9 @@ importers: happy-dom: specifier: ^17.4.7 version: 17.6.3 + msw: + specifier: ^2.10.2 + version: 2.10.2(@types/node@22.15.31)(typescript@5.8.3) tsdown: specifier: ^0.11.9 version: 0.11.13(typescript@5.8.3) @@ -44,7 +47,7 @@ importers: version: 5.8.3 vitest: specifier: ^3.1.3 - version: 3.2.3(@types/debug@4.1.12)(@types/node@22.15.31)(happy-dom@17.6.3)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0) + version: 3.2.3(@types/debug@4.1.12)(@types/node@22.15.31)(happy-dom@17.6.3)(jiti@2.4.2)(msw@2.10.2(@types/node@22.15.31)(typescript@5.8.3))(tsx@4.20.2)(yaml@2.8.0) packages: @@ -122,6 +125,15 @@ packages: cpu: [x64] os: [win32] + '@bundled-es-modules/cookie@2.0.1': + resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==} + + '@bundled-es-modules/statuses@1.0.1': + resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} + + '@bundled-es-modules/tough-cookie@0.1.6': + resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==} + '@emnapi/core@1.4.3': resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} @@ -386,6 +398,37 @@ packages: cpu: [x64] os: [win32] + '@inquirer/confirm@5.1.12': + resolution: {integrity: sha512-dpq+ielV9/bqgXRUbNH//KsY6WEw9DrGPmipkpmgC1Y46cwuBTNx7PXFWTjc3MQ+urcc0QxoVHcMI0FW4Ok0hg==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@10.1.13': + resolution: {integrity: sha512-1viSxebkYN2nJULlzCxES6G9/stgHSepZ9LqqfdIGPHj5OHhiBUXVS0a6R0bEC2A+VL4D9w6QB66ebCr6HGllA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@1.0.12': + resolution: {integrity: sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==} + engines: {node: '>=18'} + + '@inquirer/type@3.0.7': + resolution: {integrity: sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@jridgewell/gen-mapping@0.3.8': resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} @@ -404,9 +447,22 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@mswjs/interceptors@0.39.2': + resolution: {integrity: sha512-RuzCup9Ct91Y7V79xwCb146RaBRHZ7NBbrIUySumd1rpKqHL5OonaqrGIbug5hNwP/fRyxFMA6ISgw4FTtYFYg==} + engines: {node: '>=18'} + '@napi-rs/wasm-runtime@0.2.11': resolution: {integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==} + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@oxc-project/types@0.70.0': resolution: {integrity: sha512-ngyLUpUjO3dpqygSRQDx7nMx8+BmXbWOU4oIwTJFV2MVIDG7knIZwgdwXlQWLg3C3oxg1lS7ppMtPKqKFb7wzw==} @@ -583,6 +639,9 @@ packages: '@types/chai@5.2.2': resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -601,6 +660,12 @@ packages: '@types/node@22.15.31': resolution: {integrity: sha512-jnVe5ULKl6tijxUhvQeNbQG/84fHfg+yMak02cT8QVhBx/F05rAVxCGBYYTh2EKz22D6JF5ktXuNwdx7b9iEGw==} + '@types/statuses@2.0.6': + resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} + + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} @@ -633,6 +698,18 @@ packages: '@vitest/utils@3.2.3': resolution: {integrity: sha512-4zFBCU5Pf+4Z6v+rwnZ1HU1yzOKKvDkMXZrymE2PBlbjKJRlrOxbvpfPSvJTGRIwGoahaOGvp+kbCoxifhzJ1Q==} + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + ansis@4.1.0: resolution: {integrity: sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==} engines: {node: '>=14'} @@ -687,6 +764,14 @@ packages: citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -708,6 +793,10 @@ packages: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + debug@4.4.1: resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} engines: {node: '>=6.0'} @@ -748,6 +837,9 @@ packages: oxc-resolver: optional: true + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + empathic@1.1.0: resolution: {integrity: sha512-rsPft6CK3eHtrlp9Y5ALBb+hfK+DWnA4WFebbazxjWyx8vSm3rZeoM3z9irsjcqO3PYRzlfv27XIB4tz2DV7RA==} engines: {node: '>=14'} @@ -791,6 +883,10 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-tsconfig@4.10.1: resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} @@ -798,16 +894,30 @@ packages: resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} hasBin: true + graphql@16.11.0: + resolution: {integrity: sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + happy-dom@17.6.3: resolution: {integrity: sha512-UVIHeVhxmxedbWPCfgS55Jg2rDfwf2BCKeylcPSqazLz5w3Kri7Q4xdBJubsr/+VUzFLh0VjIvh13RaDA2/Xug==} engines: {node: '>=20.0.0'} + headers-polyfill@4.0.3: + resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} + hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} is-arrayish@0.3.2: resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + jiti@2.4.2: resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} hasBin: true @@ -832,6 +942,20 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + msw@2.10.2: + resolution: {integrity: sha512-RCKM6IZseZQCWcSWlutdf590M8nVfRHG1ImwzOtwz8IYxgT4zhUO0rfTcTvDGiaFE0Rhcc+h43lcF3Jc9gFtwQ==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: '>= 4.8.x' + peerDependenciesMeta: + typescript: + optional: true + + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -852,9 +976,15 @@ packages: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + package-manager-detector@1.3.0: resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -892,9 +1022,19 @@ packages: process-warning@5.0.0: resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + psl@1.15.0: + resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + quansync@0.2.10: resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} @@ -909,6 +1049,13 @@ packages: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -958,6 +1105,10 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} @@ -975,9 +1126,24 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + std-env@3.9.0: resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + strip-literal@3.0.0: resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} @@ -1009,6 +1175,10 @@ packages: resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} engines: {node: '>=14.0.0'} + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + tsdown@0.11.13: resolution: {integrity: sha512-VSfoNm8MJXFdg7PJ4p2javgjMRiQQHpkP9N3iBBTrmCixcT6YZ9ZtqYMW3NDHczqR0C0Qnur1HMQr1ZfZcmrng==} engines: {node: '>=18.0.0'} @@ -1036,6 +1206,14 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + typescript@5.8.3: resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} engines: {node: '>=14.17'} @@ -1047,6 +1225,13 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + vite-node@3.2.3: resolution: {integrity: sha512-gc8aAifGuDIpZHrPjuHyP4dpQmYXqWw7D1GmDnWeNWP654UEXzVfQ5IHPSK5HaHkwB/+p1atpYpSdw/2kOv8iQ==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -1133,6 +1318,14 @@ packages: engines: {node: '>=8'} hasBin: true + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + ws@8.18.2: resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==} engines: {node: '>=10.0.0'} @@ -1145,11 +1338,27 @@ packages: utf-8-validate: optional: true + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yaml@2.8.0: resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==} engines: {node: '>= 14.6'} hasBin: true + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yoctocolors-cjs@2.1.2: + resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} + engines: {node: '>=18'} + snapshots: '@babel/generator@7.27.5': @@ -1208,6 +1417,19 @@ snapshots: '@biomejs/cli-win32-x64@1.9.4': optional: true + '@bundled-es-modules/cookie@2.0.1': + dependencies: + cookie: 0.7.2 + + '@bundled-es-modules/statuses@1.0.1': + dependencies: + statuses: 2.0.2 + + '@bundled-es-modules/tough-cookie@0.1.6': + dependencies: + '@types/tough-cookie': 4.0.5 + tough-cookie: 4.1.4 + '@emnapi/core@1.4.3': dependencies: '@emnapi/wasi-threads': 1.0.2 @@ -1374,6 +1596,32 @@ snapshots: '@img/sharp-win32-x64@0.33.5': optional: true + '@inquirer/confirm@5.1.12(@types/node@22.15.31)': + dependencies: + '@inquirer/core': 10.1.13(@types/node@22.15.31) + '@inquirer/type': 3.0.7(@types/node@22.15.31) + optionalDependencies: + '@types/node': 22.15.31 + + '@inquirer/core@10.1.13(@types/node@22.15.31)': + dependencies: + '@inquirer/figures': 1.0.12 + '@inquirer/type': 3.0.7(@types/node@22.15.31) + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.2 + optionalDependencies: + '@types/node': 22.15.31 + + '@inquirer/figures@1.0.12': {} + + '@inquirer/type@3.0.7(@types/node@22.15.31)': + optionalDependencies: + '@types/node': 22.15.31 + '@jridgewell/gen-mapping@0.3.8': dependencies: '@jridgewell/set-array': 1.2.1 @@ -1391,6 +1639,15 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@mswjs/interceptors@0.39.2': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + '@napi-rs/wasm-runtime@0.2.11': dependencies: '@emnapi/core': 1.4.3 @@ -1398,6 +1655,15 @@ snapshots: '@tybys/wasm-util': 0.9.0 optional: true + '@open-draft/deferred-promise@2.2.0': {} + + '@open-draft/logger@0.3.0': + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + + '@open-draft/until@2.1.0': {} + '@oxc-project/types@0.70.0': {} '@quansync/fs@0.1.3': @@ -1513,6 +1779,8 @@ snapshots: dependencies: '@types/deep-eql': 4.0.2 + '@types/cookie@0.6.0': {} + '@types/debug@4.1.12': dependencies: '@types/ms': 2.1.0 @@ -1531,6 +1799,10 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/statuses@2.0.6': {} + + '@types/tough-cookie@4.0.5': {} + '@types/ws@8.18.1': dependencies: '@types/node': 22.15.31 @@ -1543,12 +1815,13 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.3(vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0))': + '@vitest/mocker@3.2.3(msw@2.10.2(@types/node@22.15.31)(typescript@5.8.3))(vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0))': dependencies: '@vitest/spy': 3.2.3 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: + msw: 2.10.2(@types/node@22.15.31)(typescript@5.8.3) vite: 6.3.5(@types/node@22.15.31)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0) '@vitest/pretty-format@3.2.3': @@ -1577,6 +1850,16 @@ snapshots: loupe: 3.1.3 tinyrainbow: 2.0.0 + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + ansis@4.1.0: {} args-tokenizer@0.3.0: {} @@ -1643,6 +1926,14 @@ snapshots: dependencies: consola: 3.4.2 + cli-width@4.1.0: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -1663,6 +1954,8 @@ snapshots: consola@3.4.2: {} + cookie@0.7.2: {} + debug@4.4.1: dependencies: ms: 2.1.3 @@ -1681,6 +1974,8 @@ snapshots: dts-resolver@2.1.1: {} + emoji-regex@8.0.0: {} + empathic@1.1.0: {} es-module-lexer@1.7.0: {} @@ -1732,6 +2027,8 @@ snapshots: fsevents@2.3.3: optional: true + get-caller-file@2.0.5: {} + get-tsconfig@4.10.1: dependencies: resolve-pkg-maps: 1.0.0 @@ -1745,15 +2042,23 @@ snapshots: nypm: 0.6.0 pathe: 2.0.3 + graphql@16.11.0: {} + happy-dom@17.6.3: dependencies: webidl-conversions: 7.0.0 whatwg-mimetype: 3.0.0 + headers-polyfill@4.0.3: {} + hookable@5.5.3: {} is-arrayish@0.3.2: {} + is-fullwidth-code-point@3.0.0: {} + + is-node-process@1.2.0: {} + jiti@2.4.2: {} js-tokens@9.0.1: {} @@ -1770,6 +2075,33 @@ snapshots: ms@2.1.3: {} + msw@2.10.2(@types/node@22.15.31)(typescript@5.8.3): + dependencies: + '@bundled-es-modules/cookie': 2.0.1 + '@bundled-es-modules/statuses': 1.0.1 + '@bundled-es-modules/tough-cookie': 0.1.6 + '@inquirer/confirm': 5.1.12(@types/node@22.15.31) + '@mswjs/interceptors': 0.39.2 + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/until': 2.1.0 + '@types/cookie': 0.6.0 + '@types/statuses': 2.0.6 + graphql: 16.11.0 + headers-polyfill: 4.0.3 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + picocolors: 1.1.1 + strict-event-emitter: 0.5.1 + type-fest: 4.41.0 + yargs: 17.7.2 + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - '@types/node' + + mute-stream@2.0.0: {} + nanoid@3.3.11: {} node-fetch-native@1.6.6: {} @@ -1786,8 +2118,12 @@ snapshots: on-exit-leak-free@2.1.2: {} + outvariant@1.4.3: {} + package-manager-detector@1.3.0: {} + path-to-regexp@6.3.0: {} + pathe@2.0.3: {} pathval@2.0.0: {} @@ -1832,8 +2168,16 @@ snapshots: process-warning@5.0.0: {} + psl@1.15.0: + dependencies: + punycode: 2.3.1 + + punycode@2.3.1: {} + quansync@0.2.10: {} + querystringify@2.2.0: {} + quick-format-unescaped@4.0.4: {} rc9@2.1.2: @@ -1845,6 +2189,10 @@ snapshots: real-require@0.2.0: {} + require-directory@2.1.1: {} + + requires-port@1.0.0: {} + resolve-pkg-maps@1.0.0: {} rolldown-plugin-dts@0.13.11(rolldown@1.0.0-beta.9)(typescript@5.8.3): @@ -1941,6 +2289,8 @@ snapshots: siginfo@2.0.0: {} + signal-exit@4.1.0: {} + simple-swizzle@0.2.2: dependencies: is-arrayish: 0.3.2 @@ -1955,8 +2305,22 @@ snapshots: stackback@0.0.2: {} + statuses@2.0.2: {} + std-env@3.9.0: {} + strict-event-emitter@0.5.1: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + strip-literal@3.0.0: dependencies: js-tokens: 9.0.1 @@ -1982,6 +2346,13 @@ snapshots: tinyspy@4.0.3: {} + tough-cookie@4.1.4: + dependencies: + psl: 1.15.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + tsdown@0.11.13(typescript@5.8.3): dependencies: ansis: 4.1.0 @@ -2016,6 +2387,10 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + type-fest@0.21.3: {} + + type-fest@4.41.0: {} + typescript@5.8.3: {} unconfig@7.3.2: @@ -2027,6 +2402,13 @@ snapshots: undici-types@6.21.0: {} + universalify@0.2.0: {} + + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + vite-node@3.2.3(@types/node@22.15.31)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0): dependencies: cac: 6.7.14 @@ -2063,11 +2445,11 @@ snapshots: tsx: 4.20.2 yaml: 2.8.0 - vitest@3.2.3(@types/debug@4.1.12)(@types/node@22.15.31)(happy-dom@17.6.3)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0): + vitest@3.2.3(@types/debug@4.1.12)(@types/node@22.15.31)(happy-dom@17.6.3)(jiti@2.4.2)(msw@2.10.2(@types/node@22.15.31)(typescript@5.8.3))(tsx@4.20.2)(yaml@2.8.0): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.3 - '@vitest/mocker': 3.2.3(vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0)) + '@vitest/mocker': 3.2.3(msw@2.10.2(@types/node@22.15.31)(typescript@5.8.3))(vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0)) '@vitest/pretty-format': 3.2.3 '@vitest/runner': 3.2.3 '@vitest/snapshot': 3.2.3 @@ -2115,6 +2497,34 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + ws@8.18.2: {} + y18n@5.0.8: {} + yaml@2.8.0: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yoctocolors-cjs@2.1.2: {} diff --git a/libs/computer/typescript/src/computer/providers/cloud.ts b/libs/computer/typescript/src/computer/providers/cloud.ts index eb1ec1b1..d8f9f1cc 100644 --- a/libs/computer/typescript/src/computer/providers/cloud.ts +++ b/libs/computer/typescript/src/computer/providers/cloud.ts @@ -69,7 +69,7 @@ export class CloudComputer extends BaseComputer { logger.info("Stopping cloud computer..."); if (this.interface) { - this.interface.close(); + this.interface.disconnect(); this.interface = undefined; } diff --git a/libs/computer/typescript/src/interface/base.ts b/libs/computer/typescript/src/interface/base.ts index aef1e2be..169cf4b0 100644 --- a/libs/computer/typescript/src/interface/base.ts +++ b/libs/computer/typescript/src/interface/base.ts @@ -91,7 +91,9 @@ export abstract class BaseComputerInterface { return; } catch (error) { // Wait a bit before retrying - this.logger.error(`Error connecting to websocket: ${error}`); + this.logger.error( + `Error connecting to websocket: ${JSON.stringify(error)}` + ); await new Promise((resolve) => setTimeout(resolve, 1000)); } } @@ -102,7 +104,7 @@ export abstract class BaseComputerInterface { /** * Connect to the WebSocket server. */ - protected async connect(): Promise { + public async connect(): Promise { if (this.ws.readyState === WebSocket.OPEN) { return; } @@ -151,7 +153,7 @@ export abstract class BaseComputerInterface { /** * Send a command to the WebSocket server. */ - protected async sendCommand(command: { + public async sendCommand(command: { action: string; [key: string]: unknown; }): Promise<{ [key: string]: unknown }> { @@ -198,13 +200,23 @@ export abstract class BaseComputerInterface { return commandPromise; } + /** + * Check if the WebSocket is connected. + */ + public isConnected(): boolean { + return this.ws && this.ws.readyState === WebSocket.OPEN; + } + /** * Close the interface connection. */ - close(): void { + disconnect(): void { this.closed = true; - if (this.ws && this.ws.readyState !== WebSocket.CLOSED) { + if (this.ws && this.ws.readyState === WebSocket.OPEN) { this.ws.close(); + } else if (this.ws && this.ws.readyState === WebSocket.CONNECTING) { + // If still connecting, terminate the connection attempt + this.ws.terminate(); } } @@ -214,7 +226,7 @@ export abstract class BaseComputerInterface { * to provide more forceful cleanup. */ forceClose(): void { - this.close(); + this.disconnect(); } // Mouse Actions diff --git a/libs/computer/typescript/tests/interface/factory.test.ts b/libs/computer/typescript/tests/interface/factory.test.ts new file mode 100644 index 00000000..8aa1a1d7 --- /dev/null +++ b/libs/computer/typescript/tests/interface/factory.test.ts @@ -0,0 +1,71 @@ +import { describe, expect, it } from "vitest"; +import { InterfaceFactory } from "../../src/interface/factory.ts"; +import { MacOSComputerInterface } from "../../src/interface/macos.ts"; +import { LinuxComputerInterface } from "../../src/interface/linux.ts"; +import { WindowsComputerInterface } from "../../src/interface/windows.ts"; +import { OSType } from "../../src/types.ts"; + +describe("InterfaceFactory", () => { + const testParams = { + ipAddress: "192.168.1.100", + username: "testuser", + password: "testpass", + apiKey: "test-api-key", + vmName: "test-vm", + }; + + describe("createInterfaceForOS", () => { + it("should create MacOSComputerInterface for macOS", () => { + const interface_ = InterfaceFactory.createInterfaceForOS( + OSType.MACOS, + testParams.ipAddress, + testParams.apiKey, + testParams.vmName + ); + + expect(interface_).toBeInstanceOf(MacOSComputerInterface); + }); + + it("should create LinuxComputerInterface for Linux", () => { + const interface_ = InterfaceFactory.createInterfaceForOS( + OSType.LINUX, + testParams.ipAddress, + testParams.apiKey, + testParams.vmName + ); + + expect(interface_).toBeInstanceOf(LinuxComputerInterface); + }); + + it("should create WindowsComputerInterface for Windows", () => { + const interface_ = InterfaceFactory.createInterfaceForOS( + OSType.WINDOWS, + testParams.ipAddress, + testParams.apiKey, + testParams.vmName + ); + + expect(interface_).toBeInstanceOf(WindowsComputerInterface); + }); + + it("should throw error for unsupported OS type", () => { + expect(() => { + InterfaceFactory.createInterfaceForOS( + "unsupported" as OSType, + testParams.ipAddress, + testParams.apiKey, + testParams.vmName + ); + }).toThrow("Unsupported OS type: unsupported"); + }); + + it("should create interface without API key and VM name", () => { + const interface_ = InterfaceFactory.createInterfaceForOS( + OSType.MACOS, + testParams.ipAddress + ); + + expect(interface_).toBeInstanceOf(MacOSComputerInterface); + }); + }); +}); diff --git a/libs/computer/typescript/tests/interface/index.test.ts b/libs/computer/typescript/tests/interface/index.test.ts new file mode 100644 index 00000000..0c8264ab --- /dev/null +++ b/libs/computer/typescript/tests/interface/index.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, it } from "vitest"; +import * as InterfaceExports from "../../src/interface/index.ts"; + +describe("Interface Module Exports", () => { + it("should export InterfaceFactory", () => { + expect(InterfaceExports.InterfaceFactory).toBeDefined(); + expect(InterfaceExports.InterfaceFactory.createInterfaceForOS).toBeDefined(); + }); + + it("should export BaseComputerInterface", () => { + expect(InterfaceExports.BaseComputerInterface).toBeDefined(); + }); + + it("should export MacOSComputerInterface", () => { + expect(InterfaceExports.MacOSComputerInterface).toBeDefined(); + }); + + it("should export LinuxComputerInterface", () => { + expect(InterfaceExports.LinuxComputerInterface).toBeDefined(); + }); + + it("should export WindowsComputerInterface", () => { + expect(InterfaceExports.WindowsComputerInterface).toBeDefined(); + }); + + it("should export all expected interfaces", () => { + const expectedExports = [ + "InterfaceFactory", + "BaseComputerInterface", + "MacOSComputerInterface", + "LinuxComputerInterface", + "WindowsComputerInterface", + ]; + + const actualExports = Object.keys(InterfaceExports); + for (const exportName of expectedExports) { + expect(actualExports).toContain(exportName); + } + }); +}); diff --git a/libs/computer/typescript/tests/interface/integration.test.ts b/libs/computer/typescript/tests/interface/integration.test.ts new file mode 100644 index 00000000..b0867ca7 --- /dev/null +++ b/libs/computer/typescript/tests/interface/integration.test.ts @@ -0,0 +1,447 @@ +import { + describe, + expect, + it, + beforeEach, + afterEach, + vi, + beforeAll, + afterAll, +} from "vitest"; +import { InterfaceFactory } from "../../src/interface/factory.ts"; +import { OSType } from "../../src/types.ts"; +import { ws } from "msw"; +import { setupServer } from "msw/node"; + +describe("Interface Integration Tests", () => { + const testIp = "192.168.1.100"; + const testPort = 8000; + + // Create WebSocket server + const server = setupServer(); + + beforeAll(() => { + server.listen({ onUnhandledRequest: "error" }); + }); + + afterAll(() => { + server.close(); + }); + + beforeEach(() => { + // Reset handlers for each test + server.resetHandlers(); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe("Cross-platform interface creation", () => { + it("should create correct interface for each OS type", async () => { + const osTypes = [OSType.MACOS, OSType.LINUX, OSType.WINDOWS]; + const interfaces: Array<{ + os: OSType; + interface: ReturnType; + }> = []; + + // Create interfaces for each OS + for (const os of osTypes) { + const interface_ = InterfaceFactory.createInterfaceForOS(os, testIp); + interfaces.push({ os, interface: interface_ }); + } + + // Verify each interface is created correctly + expect(interfaces).toHaveLength(3); + for (const { os, interface: iface } of interfaces) { + expect(iface).toBeDefined(); + // Check that the interface name contains the OS type in some form + const osName = os.toLowerCase(); + expect(iface.constructor.name.toLowerCase()).toContain(osName); + } + }); + + it("should handle multiple interfaces with different IPs", async () => { + const ips = ["192.168.1.100", "192.168.1.101", "192.168.1.102"]; + const interfaces = ips.map((ip) => + InterfaceFactory.createInterfaceForOS(OSType.MACOS, ip) + ); + + // Set up WebSocket handlers for each IP + for (const ip of ips) { + const wsLink = ws.link(`ws://${ip}:${testPort}/ws`); + server.use( + wsLink.addEventListener("connection", ({ client }) => { + client.addEventListener("message", () => { + // Echo back success response + client.send(JSON.stringify({ success: true })); + }); + }) + ); + } + + // Connect all interfaces + await Promise.all(interfaces.map((iface) => iface.connect())); + + // Verify all are connected + for (const iface of interfaces) { + expect(iface.isConnected()).toBe(true); + } + + // Clean up + for (const iface of interfaces) { + iface.disconnect(); + } + }); + }); + + describe("Connection management", () => { + it("should handle connection lifecycle", async () => { + // Set up WebSocket handler + const wsLink = ws.link(`ws://${testIp}:${testPort}/ws`); + server.use( + wsLink.addEventListener("connection", ({ client }) => { + client.addEventListener("message", () => { + // Echo back success response + client.send(JSON.stringify({ success: true })); + }); + }) + ); + + const interface_ = InterfaceFactory.createInterfaceForOS( + OSType.MACOS, + testIp + ); + + // Initially not connected + expect(interface_.isConnected()).toBe(false); + + // Connect + await interface_.connect(); + expect(interface_.isConnected()).toBe(true); + + // Disconnect + interface_.disconnect(); + + // Wait a tick for the close to process + await new Promise((resolve) => process.nextTick(resolve)); + expect(interface_.isConnected()).toBe(false); + }); + + it("should handle connection errors gracefully", async () => { + // Don't register a handler - connection will succeed but no responses + const interface_ = InterfaceFactory.createInterfaceForOS( + OSType.MACOS, + "192.0.2.1" // TEST-NET-1 address + ); + + // Should connect (WebSocket mock always connects) + await interface_.connect(); + expect(interface_.isConnected()).toBe(true); + + interface_.disconnect(); + }); + + it("should handle secure connections", async () => { + const secureIp = "192.0.2.1"; + const securePort = 8443; + + // Register handler for secure connection + const wsLink = ws.link(`wss://${secureIp}:${securePort}/ws`); + server.use( + wsLink.addEventListener("connection", ({ client }) => { + client.addEventListener("message", () => { + // Echo back success response + client.send(JSON.stringify({ success: true })); + }); + }) + ); + + const interface_ = InterfaceFactory.createInterfaceForOS( + OSType.MACOS, + secureIp, + "testuser", + "testpass" + ); + + await interface_.connect(); + expect(interface_.isConnected()).toBe(true); + + interface_.disconnect(); + }); + }); + + describe("Performance and concurrency", () => { + it("should handle rapid command sequences", async () => { + const receivedCommands: string[] = []; + + // Set up WebSocket handler that tracks commands + const wsLink = ws.link(`ws://${testIp}:${testPort}/ws`); + server.use( + wsLink.addEventListener("connection", ({ client }) => { + client.addEventListener("message", (event) => { + const data = JSON.parse(event.data as string); + receivedCommands.push(data.action); + // Send response with command index + client.send( + JSON.stringify({ + success: true, + data: `Response for ${data.action}`, + }) + ); + }); + }) + ); + + const interface_ = InterfaceFactory.createInterfaceForOS( + OSType.MACOS, + testIp + ); + await interface_.connect(); + + // Send multiple commands rapidly + const commands = ["left_click", "right_click", "double_click"]; + const promises = commands.map((cmd) => { + switch (cmd) { + case "left_click": + return interface_.leftClick(100, 200); + case "right_click": + return interface_.rightClick(150, 250); + case "double_click": + return interface_.doubleClick(200, 300); + } + }); + + await Promise.all(promises); + + // Verify all commands were received + expect(receivedCommands).toHaveLength(3); + expect(receivedCommands).toContain("left_click"); + expect(receivedCommands).toContain("right_click"); + expect(receivedCommands).toContain("double_click"); + + interface_.disconnect(); + }); + + it("should maintain command order with locking", async () => { + const receivedCommands: Array<{ action: string; index: number }> = []; + + // Set up WebSocket handler that tracks commands with delay + const wsLink = ws.link(`ws://${testIp}:${testPort}/ws`); + server.use( + wsLink.addEventListener("connection", ({ client }) => { + client.addEventListener("message", async (event) => { + // Add delay to simulate processing + await new Promise((resolve) => setTimeout(resolve, 10)); + + const data = JSON.parse(event.data as string); + receivedCommands.push({ + action: data.action, + index: data.index, + }); + + client.send(JSON.stringify({ success: true })); + }); + }) + ); + + const interface_ = InterfaceFactory.createInterfaceForOS( + OSType.MACOS, + testIp + ); + await interface_.connect(); + + // Helper to send command with index + async function sendCommandWithIndex(action: string, index: number) { + await interface_.sendCommand({ action, index }); + } + + // Send commands in sequence + await sendCommandWithIndex("command1", 0); + await sendCommandWithIndex("command2", 1); + await sendCommandWithIndex("command3", 2); + + // Wait for all commands to be processed + await new Promise((resolve) => setTimeout(resolve, 50)); + + // Verify commands were received in order + expect(receivedCommands).toHaveLength(3); + expect(receivedCommands[0]).toEqual({ action: "command1", index: 0 }); + expect(receivedCommands[1]).toEqual({ action: "command2", index: 1 }); + expect(receivedCommands[2]).toEqual({ action: "command3", index: 2 }); + + interface_.disconnect(); + }); + }); + + describe("Error handling", () => { + it("should handle command failures", async () => { + // Set up WebSocket handler that returns errors + const wsLink = ws.link(`ws://${testIp}:${testPort}/ws`); + server.use( + wsLink.addEventListener("connection", ({ client }) => { + client.addEventListener("message", (event) => { + const data = JSON.parse(event.data as string); + + if (data.action === "fail_command") { + client.send( + JSON.stringify({ + success: false, + error: "Command failed", + }) + ); + } else { + client.send(JSON.stringify({ success: true })); + } + }); + }) + ); + + const interface_ = InterfaceFactory.createInterfaceForOS( + OSType.MACOS, + testIp + ); + await interface_.connect(); + + // Send a failing command + await expect( + interface_.sendCommand({ action: "fail_command" }) + ).rejects.toThrow("Command failed"); + + // Verify interface is still connected + expect(interface_.isConnected()).toBe(true); + + // Send a successful command + const result = await interface_.sendCommand({ + action: "success_command", + }); + expect(result.success).toBe(true); + + interface_.disconnect(); + }); + + it("should handle disconnection during command", async () => { + // Set up WebSocket handler that captures WebSocket instance + const wsLink = ws.link(`ws://${testIp}:${testPort}/ws`); + server.use( + wsLink.addEventListener("connection", ({ client }) => { + client.addEventListener("message", async () => { + // Simulate disconnection during command processing + await new Promise((resolve) => setTimeout(resolve, 10)); + client.close(); + }); + }) + ); + + const interface_ = InterfaceFactory.createInterfaceForOS( + OSType.MACOS, + testIp + ); + await interface_.connect(); + + // Send command that will trigger disconnection + await expect( + interface_.sendCommand({ action: "disconnect_me" }) + ).rejects.toThrow(); + + // Wait for close to process + await new Promise((resolve) => setTimeout(resolve, 20)); + + // Verify interface is disconnected + expect(interface_.isConnected()).toBe(false); + }); + }); + + describe("Feature-specific tests", () => { + it("should handle screenshot commands", async () => { + // Set up WebSocket handler + const wsLink = ws.link(`ws://${testIp}:${testPort}/ws`); + server.use( + wsLink.addEventListener("connection", ({ client }) => { + client.addEventListener("message", () => { + // Echo back success response with screenshot data + client.send(JSON.stringify({ + success: true, + data: "base64encodedimage" + })); + }); + }) + ); + + const interface_ = InterfaceFactory.createInterfaceForOS( + OSType.MACOS, + testIp + ); + await interface_.connect(); + + const screenshot = await interface_.screenshot(); + expect(screenshot).toBeInstanceOf(Buffer); + expect(screenshot.toString("base64")).toBe("base64encodedimage"); + + interface_.disconnect(); + }); + + it("should handle screen size queries", async () => { + // Set up WebSocket handler + const wsLink = ws.link(`ws://${testIp}:${testPort}/ws`); + server.use( + wsLink.addEventListener("connection", ({ client }) => { + client.addEventListener("message", () => { + // Echo back success response with screen size + client.send(JSON.stringify({ + success: true, + data: { width: 1920, height: 1080 } + })); + }); + }) + ); + + const interface_ = InterfaceFactory.createInterfaceForOS( + OSType.LINUX, + testIp + ); + await interface_.connect(); + + const screenSize = await interface_.getScreenSize(); + expect(screenSize).toEqual({ width: 1920, height: 1080 }); + + interface_.disconnect(); + }); + + it("should handle file operations", async () => { + // Set up WebSocket handler + const wsLink = ws.link(`ws://${testIp}:${testPort}/ws`); + server.use( + wsLink.addEventListener("connection", ({ client }) => { + client.addEventListener("message", () => { + // Echo back success response with file data + client.send(JSON.stringify({ + success: true, + data: "file content" + })); + }); + }) + ); + + const interface_ = InterfaceFactory.createInterfaceForOS( + OSType.WINDOWS, + testIp + ); + await interface_.connect(); + + // Test file exists + const exists = await interface_.fileExists("/test/file.txt"); + expect(exists).toBe(true); + + // Test read text + const content = await interface_.readText("/test/file.txt"); + expect(content).toBe("file content"); + + // Test list directory + const files = await interface_.listDir("/test"); + expect(files).toEqual(["file1.txt", "file2.txt", "dir1"]); + + interface_.disconnect(); + }); + }); +}); diff --git a/libs/computer/typescript/tests/interface/linux.test.ts b/libs/computer/typescript/tests/interface/linux.test.ts new file mode 100644 index 00000000..d5d8532b --- /dev/null +++ b/libs/computer/typescript/tests/interface/linux.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, it } from "vitest"; +import { LinuxComputerInterface } from "../../src/interface/linux.ts"; +import { MacOSComputerInterface } from "../../src/interface/macos.ts"; + +describe("LinuxComputerInterface", () => { + const testParams = { + ipAddress: "192.0.2.1", // TEST-NET-1 address (RFC 5737) - guaranteed not to be routable + username: "testuser", + password: "testpass", + apiKey: "test-api-key", + vmName: "test-vm", + }; + + describe("Inheritance", () => { + it("should extend MacOSComputerInterface", () => { + const linuxInterface = new LinuxComputerInterface( + testParams.ipAddress, + testParams.username, + testParams.password, + testParams.apiKey, + testParams.vmName + ); + + expect(linuxInterface).toBeInstanceOf(MacOSComputerInterface); + expect(linuxInterface).toBeInstanceOf(LinuxComputerInterface); + }); + }); +}); diff --git a/libs/computer/typescript/tests/interface/macos.test.ts b/libs/computer/typescript/tests/interface/macos.test.ts new file mode 100644 index 00000000..fb7e4b08 --- /dev/null +++ b/libs/computer/typescript/tests/interface/macos.test.ts @@ -0,0 +1,444 @@ +import { + describe, + expect, + it, + beforeEach, + afterEach, +} from "vitest"; +import { MacOSComputerInterface } from "../../src/interface/macos.ts"; +// Import the setup.ts which already has MSW configured +import "../setup.ts"; + +describe("MacOSComputerInterface", () => { + // Define test parameters + const testParams = { + ipAddress: "192.0.2.1", // TEST-NET-1 address (RFC 5737) - guaranteed not to be routable + username: "testuser", + password: "testpass", + apiKey: "test-api-key", + vmName: "test-vm", + }; + + // Track received messages for verification + // biome-ignore lint/suspicious/noExplicitAny: + let receivedMessages: any[] = []; + + beforeEach(() => { + // Clear received messages before each test + receivedMessages = []; + }); + + afterEach(() => { + // Clear any state after each test + receivedMessages = []; + }); + + describe("Connection Management", () => { + it("should connect with proper authentication headers", async () => { + const macosInterface = new MacOSComputerInterface( + testParams.ipAddress, + testParams.username, + testParams.password, + testParams.apiKey, + testParams.vmName + ); + + await macosInterface.connect(); + + // Verify the interface is connected + expect(macosInterface.isConnected()).toBe(true); + + await macosInterface.disconnect(); + }); + }); + + describe("Mouse Actions", () => { + let macosInterface: MacOSComputerInterface; + + beforeEach(async () => { + macosInterface = new MacOSComputerInterface( + testParams.ipAddress, + testParams.username, + testParams.password, + testParams.apiKey, + testParams.vmName + ); + // Remove initialize() call - connection happens on first command + await new Promise((resolve) => setTimeout(resolve, 100)); + }); + + afterEach(async () => { + await macosInterface.disconnect(); + }); + + it("should send mouse_down command", async () => { + await macosInterface.mouseDown(100, 200, "left"); + + expect(receivedMessages).toHaveLength(1); + expect(receivedMessages[0]).toEqual({ + action: "mouse_down", + x: 100, + y: 200, + button: "left", + }); + }); + + it("should send mouse_up command", async () => { + await macosInterface.mouseUp(100, 200, "right"); + + expect(receivedMessages).toHaveLength(1); + expect(receivedMessages[0]).toEqual({ + action: "mouse_up", + x: 100, + y: 200, + button: "right", + }); + }); + + it("should send left_click command", async () => { + await macosInterface.leftClick(150, 250); + + expect(receivedMessages).toHaveLength(1); + expect(receivedMessages[0]).toEqual({ + action: "left_click", + x: 150, + y: 250, + }); + }); + + it("should send right_click command", async () => { + await macosInterface.rightClick(150, 250); + + expect(receivedMessages).toHaveLength(1); + expect(receivedMessages[0]).toEqual({ + action: "right_click", + x: 150, + y: 250, + }); + }); + + it("should send double_click command", async () => { + await macosInterface.doubleClick(150, 250); + + expect(receivedMessages).toHaveLength(1); + expect(receivedMessages[0]).toEqual({ + action: "double_click", + x: 150, + y: 250, + }); + }); + + it("should send move_cursor command", async () => { + await macosInterface.moveCursor(300, 400); + + expect(receivedMessages).toHaveLength(1); + expect(receivedMessages[0]).toEqual({ + action: "move_cursor", + x: 300, + y: 400, + }); + }); + + it("should send drag_to command", async () => { + await macosInterface.dragTo(500, 600, "left", 1.5); + + expect(receivedMessages).toHaveLength(1); + expect(receivedMessages[0]).toEqual({ + action: "drag_to", + x: 500, + y: 600, + button: "left", + duration: 1.5, + }); + }); + + it("should send scroll command", async () => { + await macosInterface.scroll(0, 10); + + expect(receivedMessages).toHaveLength(1); + expect(receivedMessages[0]).toEqual({ + action: "scroll", + x: 0, + y: 10, + clicks: 5, + }); + }); + }); + + describe("Keyboard Actions", () => { + let macosInterface: MacOSComputerInterface; + + beforeEach(async () => { + macosInterface = new MacOSComputerInterface( + testParams.ipAddress, + testParams.username, + testParams.password, + testParams.apiKey, + testParams.vmName + ); + // Remove initialize() call + await new Promise((resolve) => setTimeout(resolve, 100)); + }); + + afterEach(async () => { + await macosInterface.disconnect(); + }); + + it("should send key_down command", async () => { + await macosInterface.keyDown("a"); + + expect(receivedMessages).toHaveLength(1); + expect(receivedMessages[0]).toEqual({ + action: "key_down", + key: "a", + }); + }); + + it("should send key_up command", async () => { + await macosInterface.keyUp("a"); + + expect(receivedMessages).toHaveLength(1); + expect(receivedMessages[0]).toEqual({ + action: "key_up", + key: "a", + }); + }); + + it("should send key_press command", async () => { + await macosInterface.keyDown("enter"); + + expect(receivedMessages).toHaveLength(1); + expect(receivedMessages[0]).toEqual({ + action: "key_press", + key: "enter", + }); + }); + + it("should send type_text command", async () => { + await macosInterface.typeText("Hello, World!"); + + expect(receivedMessages).toHaveLength(1); + expect(receivedMessages[0]).toEqual({ + action: "type_text", + text: "Hello, World!", + }); + }); + + it("should send hotkey command", async () => { + await macosInterface.hotkey("cmd", "c"); + + expect(receivedMessages).toHaveLength(1); + expect(receivedMessages[0]).toEqual({ + action: "hotkey", + keys: ["cmd", "c"], + }); + }); + }); + + describe("Screen Actions", () => { + let macosInterface: MacOSComputerInterface; + + beforeEach(async () => { + macosInterface = new MacOSComputerInterface( + testParams.ipAddress, + testParams.username, + testParams.password, + testParams.apiKey, + testParams.vmName + ); + // Remove initialize() call + await new Promise((resolve) => setTimeout(resolve, 100)); + }); + + afterEach(async () => { + await macosInterface.disconnect(); + }); + + it("should get screenshot", async () => { + const screenshot = await macosInterface.screenshot(); + + expect(receivedMessages).toHaveLength(1); + expect(receivedMessages[0]).toEqual({ + action: "screenshot", + }); + expect(screenshot).toBe("base64encodedimage"); + }); + + it("should get screen size", async () => { + const screenSize = await macosInterface.getScreenSize(); + + expect(receivedMessages).toHaveLength(1); + expect(receivedMessages[0]).toEqual({ + action: "get_screen_size", + }); + expect(screenSize).toEqual({ width: 1920, height: 1080 }); + }); + + it("should get cursor position", async () => { + const position = await macosInterface.getCursorPosition(); + + expect(receivedMessages).toHaveLength(1); + expect(receivedMessages[0]).toEqual({ + action: "get_cursor_position", + }); + expect(position).toEqual({ x: 100, y: 200 }); + }); + + it("should get accessibility tree", async () => { + const tree = await macosInterface.getAccessibilityTree(); + + expect(receivedMessages).toHaveLength(1); + expect(receivedMessages[0]).toEqual({ + action: "get_accessibility_tree", + }); + expect(tree).toEqual({ + role: "window", + title: "Test Window", + children: [], + }); + }); + }); + + describe("System Actions", () => { + let macosInterface: MacOSComputerInterface; + + beforeEach(async () => { + macosInterface = new MacOSComputerInterface( + testParams.ipAddress, + testParams.username, + testParams.password, + testParams.apiKey, + testParams.vmName + ); + // Remove initialize() call + await new Promise((resolve) => setTimeout(resolve, 100)); + }); + + afterEach(async () => { + await macosInterface.disconnect(); + }); + + it("should run command", async () => { + const result = await macosInterface.runCommand("ls -la"); + + expect(receivedMessages).toHaveLength(1); + expect(receivedMessages[0]).toEqual({ + action: "run_command", + command: "ls -la", + }); + expect(result).toEqual({ + stdout: "command output", + stderr: "", + returncode: 0, + }); + }); + }); + + describe("Error Handling", () => { + it("should handle WebSocket connection errors", async () => { + // Use a valid but unreachable IP to avoid DNS errors + const macosInterface = new MacOSComputerInterface( + testParams.ipAddress, + testParams.username, + testParams.password, + testParams.apiKey, + testParams.vmName + ); + + // Try to send a command - should fail with connection error + await expect(macosInterface.screenshot()).rejects.toThrow(); + + await macosInterface.disconnect(); + }); + + it("should handle server error responses", async () => { + // Override the handler to send error response + // server.use( + // chat.addEventListener("connection", ({ client, server }) => { + // client.addEventListener("message", () => { + // server.send( + // JSON.stringify({ + // success: false, + // error: "Command failed", + // }) + // ); + // }); + // }) + // ); + + const macosInterface = new MacOSComputerInterface( + testParams.ipAddress, + testParams.username, + testParams.password, + testParams.apiKey, + testParams.vmName + ); + // Remove initialize() call + await new Promise((resolve) => setTimeout(resolve, 100)); + + await expect(macosInterface.screenshot()).rejects.toThrow( + "Command failed" + ); + + await macosInterface.disconnect(); + }); + + it("should handle closed connection", async () => { + const macosInterface = new MacOSComputerInterface( + testParams.ipAddress, + testParams.username, + testParams.password, + testParams.apiKey, + testParams.vmName + ); + + // Send a command to trigger connection + await macosInterface.screenshot(); + + // Close the interface + await macosInterface.disconnect(); + + // Try to use after closing + await expect(macosInterface.screenshot()).rejects.toThrow( + "Interface is closed" + ); + }); + }); + + describe("Command Locking", () => { + let macosInterface: MacOSComputerInterface; + + beforeEach(async () => { + macosInterface = new MacOSComputerInterface( + testParams.ipAddress, + testParams.username, + testParams.password, + testParams.apiKey, + testParams.vmName + ); + // Remove initialize() call + await new Promise((resolve) => setTimeout(resolve, 100)); + }); + + afterEach(async () => { + await macosInterface.disconnect(); + }); + + it("should serialize commands", async () => { + // Send multiple commands simultaneously + const promises = [ + macosInterface.leftClick(100, 100), + macosInterface.rightClick(200, 200), + macosInterface.typeText("test"), + ]; + + await Promise.all(promises); + + // Commands should be sent in order + expect(receivedMessages).toHaveLength(3); + expect(receivedMessages[0].action).toBe("left_click"); + expect(receivedMessages[1].action).toBe("right_click"); + expect(receivedMessages[2].action).toBe("type_text"); + }); + }); +}); diff --git a/libs/computer/typescript/tests/interface/windows.test.ts b/libs/computer/typescript/tests/interface/windows.test.ts new file mode 100644 index 00000000..cf8fe9e9 --- /dev/null +++ b/libs/computer/typescript/tests/interface/windows.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, it } from "vitest"; +import { WindowsComputerInterface } from "../../src/interface/windows.ts"; +import { MacOSComputerInterface } from "../../src/interface/macos.ts"; + +describe("WindowsComputerInterface", () => { + const testParams = { + ipAddress: "192.0.2.1", // TEST-NET-1 address (RFC 5737) - guaranteed not to be routable + username: "testuser", + password: "testpass", + apiKey: "test-api-key", + vmName: "test-vm", + }; + + describe("Inheritance", () => { + it("should extend MacOSComputerInterface", () => { + const windowsInterface = new WindowsComputerInterface( + testParams.ipAddress, + testParams.username, + testParams.password, + testParams.apiKey, + testParams.vmName + ); + + expect(windowsInterface).toBeInstanceOf(MacOSComputerInterface); + expect(windowsInterface).toBeInstanceOf(WindowsComputerInterface); + }); + }); +}); diff --git a/libs/computer/typescript/tests/setup.ts b/libs/computer/typescript/tests/setup.ts new file mode 100644 index 00000000..42b09201 --- /dev/null +++ b/libs/computer/typescript/tests/setup.ts @@ -0,0 +1,26 @@ +import { afterAll, afterEach, beforeAll } from "vitest"; +import { setupServer } from "msw/node"; +import { ws } from "msw"; + +const chat = ws.link("wss://chat.example.com"); + +const wsHandlers = [ + chat.addEventListener("connection", ({ client }) => { + client.addEventListener("message", (event) => { + console.log("Received message from client:", event.data); + // Echo the received message back to the client + client.send(`Server received: ${event.data}`); + }); + }), +]; + +const server = setupServer(...wsHandlers); + +// Start server before all tests +beforeAll(() => server.listen({ onUnhandledRequest: "error" })); + +// Close server after all tests +afterAll(() => server.close()); + +// Reset handlers after each test for test isolation +afterEach(() => server.resetHandlers()); diff --git a/libs/computer/typescript/vitest.config.ts b/libs/computer/typescript/vitest.config.ts index abed6b21..4f76f6cc 100644 --- a/libs/computer/typescript/vitest.config.ts +++ b/libs/computer/typescript/vitest.config.ts @@ -1,3 +1,9 @@ -import { defineConfig } from 'vitest/config' +import { defineConfig } from "vitest/config"; -export default defineConfig({}) +export default defineConfig({ + test: { + setupFiles: ["./tests/setup.ts"], + environment: "node", + globals: true, + }, +}); From 7bda09fca5c3fcf3d9c27f6a89a7f9f38633abd3 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Thu, 19 Jun 2025 09:04:51 -0400 Subject: [PATCH 071/141] Made lume provider visible in docker agent UI --- libs/agent/agent/ui/gradio/app.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libs/agent/agent/ui/gradio/app.py b/libs/agent/agent/ui/gradio/app.py index 0593d776..bafa6c3e 100644 --- a/libs/agent/agent/ui/gradio/app.py +++ b/libs/agent/agent/ui/gradio/app.py @@ -132,6 +132,10 @@ class GradioChatScreenshotHandler(DefaultCallbackHandler): # Detect if current device is MacOS is_mac = platform.system().lower() == "darwin" +# Detect if lume is available (host device is macOS) +is_arm64 = platform.machine().lower() == "arm64" +is_lume_available = is_mac or (is_arm64 and os.environ.get("LUME_HOST", "localhost") != "localhost") + # Map model names to specific provider model names MODEL_MAPPINGS = { "openai": { @@ -733,9 +737,9 @@ if __name__ == "__main__": is_mac = platform.system().lower() == "darwin" providers = ["cloud"] - if is_mac: + if is_lume_available: providers += ["lume"] - elif is_windows: + if is_windows: providers += ["winsandbox"] computer_provider = gr.Radio( From 552e3c550c35e77f19a42d6863d7928ca909aaee Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Thu, 19 Jun 2025 09:12:38 -0400 Subject: [PATCH 072/141] Keep demo dir --- scripts/playground-docker.sh | 45 +++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/scripts/playground-docker.sh b/scripts/playground-docker.sh index 191878c5..61b57901 100644 --- a/scripts/playground-docker.sh +++ b/scripts/playground-docker.sh @@ -56,9 +56,21 @@ print_success "Docker is installed and running!" # Save the original working directory ORIGINAL_DIR="$(pwd)" -# Directories used by the script DEMO_DIR="$HOME/.cua" -REPO_DIR="$DEMO_DIR/cua" +mkdir -p "$DEMO_DIR" + + +# Check if we're already in the cua repository +# Look for the specific trycua identifier in pyproject.toml +if [[ -f "pyproject.toml" ]] && grep -q "gh@trycua.com" "pyproject.toml"; then + print_success "Already in C/ua repository - using current directory" + REPO_DIR="$ORIGINAL_DIR" + USE_EXISTING_REPO=true +else + # Directories used by the script when not in repo + REPO_DIR="$DEMO_DIR/cua" + USE_EXISTING_REPO=false +fi # Function to clean up on exit cleanup() { @@ -167,22 +179,27 @@ else exit 1 fi -# Create demo directory -mkdir -p "$DEMO_DIR" +print_success "All checks passed! 🎉" -# Clone or update the repository -if [[ ! -d "$REPO_DIR" ]]; then - print_info "Cloning C/ua repository..." - cd "$DEMO_DIR" - git clone https://github.com/trycua/cua.git -else - print_info "Updating C/ua repository..." +# Create demo directory and handle repository +if [[ "$USE_EXISTING_REPO" == "true" ]]; then + print_info "Using existing repository in current directory" + cd "$REPO_DIR" +else + # Clone or update the repository + if [[ ! -d "$REPO_DIR" ]]; then + print_info "Cloning C/ua repository..." + cd "$DEMO_DIR" + git clone https://github.com/trycua/cua.git + else + print_info "Updating C/ua repository..." + cd "$REPO_DIR" + git pull origin main + fi + cd "$REPO_DIR" - git pull origin main fi -cd "$REPO_DIR" - # Create .env.local file with API keys ENV_FILE="$REPO_DIR/.env.local" if [[ ! -f "$ENV_FILE" ]]; then From ee97e4cbb55059afc7e2e65e9bcbd4dccee9f43b Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Thu, 19 Jun 2025 09:44:59 -0400 Subject: [PATCH 073/141] Detect if PYLUME_HOST is set --- libs/agent/agent/ui/gradio/app.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libs/agent/agent/ui/gradio/app.py b/libs/agent/agent/ui/gradio/app.py index bafa6c3e..f52a3222 100644 --- a/libs/agent/agent/ui/gradio/app.py +++ b/libs/agent/agent/ui/gradio/app.py @@ -133,8 +133,11 @@ class GradioChatScreenshotHandler(DefaultCallbackHandler): is_mac = platform.system().lower() == "darwin" # Detect if lume is available (host device is macOS) -is_arm64 = platform.machine().lower() == "arm64" -is_lume_available = is_mac or (is_arm64 and os.environ.get("LUME_HOST", "localhost") != "localhost") +is_lume_available = is_mac or (os.environ.get("PYLUME_HOST", "localhost") != "localhost") + +print("PYLUME_HOST: ", os.environ.get("PYLUME_HOST", "localhost")) +print("is_mac: ", is_mac) +print("Lume available: ", is_lume_available) # Map model names to specific provider model names MODEL_MAPPINGS = { From 613745da7f118029102f10ac122ece6cae99c22b Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Thu, 19 Jun 2025 14:48:38 -0700 Subject: [PATCH 074/141] Finish writing tests for interfaces --- libs/computer/typescript/package.json | 1 - libs/computer/typescript/pnpm-lock.yaml | 116 ++- .../computer/typescript/src/interface/base.ts | 17 +- .../tests/interface/integration.test.ts | 447 ---------- .../typescript/tests/interface/linux.test.ts | 2 +- .../typescript/tests/interface/macos.test.ts | 827 +++++++++++++----- libs/computer/typescript/tests/setup.ts | 25 +- 7 files changed, 707 insertions(+), 728 deletions(-) delete mode 100644 libs/computer/typescript/tests/interface/integration.test.ts diff --git a/libs/computer/typescript/package.json b/libs/computer/typescript/package.json index 8ff5c095..71141f22 100644 --- a/libs/computer/typescript/package.json +++ b/libs/computer/typescript/package.json @@ -48,7 +48,6 @@ "@types/ws": "^8.18.1", "bumpp": "^10.1.0", "happy-dom": "^17.4.7", - "msw": "^2.10.2", "tsdown": "^0.11.9", "tsx": "^4.19.4", "typescript": "^5.8.3", diff --git a/libs/computer/typescript/pnpm-lock.yaml b/libs/computer/typescript/pnpm-lock.yaml index 1abeebdc..9104a636 100644 --- a/libs/computer/typescript/pnpm-lock.yaml +++ b/libs/computer/typescript/pnpm-lock.yaml @@ -33,9 +33,6 @@ importers: happy-dom: specifier: ^17.4.7 version: 17.6.3 - msw: - specifier: ^2.10.2 - version: 2.10.2(@types/node@22.15.31)(typescript@5.8.3) tsdown: specifier: ^0.11.9 version: 0.11.13(typescript@5.8.3) @@ -1420,15 +1417,18 @@ snapshots: '@bundled-es-modules/cookie@2.0.1': dependencies: cookie: 0.7.2 + optional: true '@bundled-es-modules/statuses@1.0.1': dependencies: statuses: 2.0.2 + optional: true '@bundled-es-modules/tough-cookie@0.1.6': dependencies: '@types/tough-cookie': 4.0.5 tough-cookie: 4.1.4 + optional: true '@emnapi/core@1.4.3': dependencies: @@ -1602,6 +1602,7 @@ snapshots: '@inquirer/type': 3.0.7(@types/node@22.15.31) optionalDependencies: '@types/node': 22.15.31 + optional: true '@inquirer/core@10.1.13(@types/node@22.15.31)': dependencies: @@ -1615,12 +1616,15 @@ snapshots: yoctocolors-cjs: 2.1.2 optionalDependencies: '@types/node': 22.15.31 + optional: true - '@inquirer/figures@1.0.12': {} + '@inquirer/figures@1.0.12': + optional: true '@inquirer/type@3.0.7(@types/node@22.15.31)': optionalDependencies: '@types/node': 22.15.31 + optional: true '@jridgewell/gen-mapping@0.3.8': dependencies: @@ -1647,6 +1651,7 @@ snapshots: is-node-process: 1.2.0 outvariant: 1.4.3 strict-event-emitter: 0.5.1 + optional: true '@napi-rs/wasm-runtime@0.2.11': dependencies: @@ -1655,14 +1660,17 @@ snapshots: '@tybys/wasm-util': 0.9.0 optional: true - '@open-draft/deferred-promise@2.2.0': {} + '@open-draft/deferred-promise@2.2.0': + optional: true '@open-draft/logger@0.3.0': dependencies: is-node-process: 1.2.0 outvariant: 1.4.3 + optional: true - '@open-draft/until@2.1.0': {} + '@open-draft/until@2.1.0': + optional: true '@oxc-project/types@0.70.0': {} @@ -1779,7 +1787,8 @@ snapshots: dependencies: '@types/deep-eql': 4.0.2 - '@types/cookie@0.6.0': {} + '@types/cookie@0.6.0': + optional: true '@types/debug@4.1.12': dependencies: @@ -1799,9 +1808,11 @@ snapshots: dependencies: undici-types: 6.21.0 - '@types/statuses@2.0.6': {} + '@types/statuses@2.0.6': + optional: true - '@types/tough-cookie@4.0.5': {} + '@types/tough-cookie@4.0.5': + optional: true '@types/ws@8.18.1': dependencies: @@ -1853,12 +1864,15 @@ snapshots: ansi-escapes@4.3.2: dependencies: type-fest: 0.21.3 + optional: true - ansi-regex@5.0.1: {} + ansi-regex@5.0.1: + optional: true ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 + optional: true ansis@4.1.0: {} @@ -1926,13 +1940,15 @@ snapshots: dependencies: consola: 3.4.2 - cli-width@4.1.0: {} + cli-width@4.1.0: + optional: true cliui@8.0.1: dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + optional: true color-convert@2.0.1: dependencies: @@ -1954,7 +1970,8 @@ snapshots: consola@3.4.2: {} - cookie@0.7.2: {} + cookie@0.7.2: + optional: true debug@4.4.1: dependencies: @@ -1974,7 +1991,8 @@ snapshots: dts-resolver@2.1.1: {} - emoji-regex@8.0.0: {} + emoji-regex@8.0.0: + optional: true empathic@1.1.0: {} @@ -2027,7 +2045,8 @@ snapshots: fsevents@2.3.3: optional: true - get-caller-file@2.0.5: {} + get-caller-file@2.0.5: + optional: true get-tsconfig@4.10.1: dependencies: @@ -2042,22 +2061,26 @@ snapshots: nypm: 0.6.0 pathe: 2.0.3 - graphql@16.11.0: {} + graphql@16.11.0: + optional: true happy-dom@17.6.3: dependencies: webidl-conversions: 7.0.0 whatwg-mimetype: 3.0.0 - headers-polyfill@4.0.3: {} + headers-polyfill@4.0.3: + optional: true hookable@5.5.3: {} is-arrayish@0.3.2: {} - is-fullwidth-code-point@3.0.0: {} + is-fullwidth-code-point@3.0.0: + optional: true - is-node-process@1.2.0: {} + is-node-process@1.2.0: + optional: true jiti@2.4.2: {} @@ -2099,8 +2122,10 @@ snapshots: typescript: 5.8.3 transitivePeerDependencies: - '@types/node' + optional: true - mute-stream@2.0.0: {} + mute-stream@2.0.0: + optional: true nanoid@3.3.11: {} @@ -2118,11 +2143,13 @@ snapshots: on-exit-leak-free@2.1.2: {} - outvariant@1.4.3: {} + outvariant@1.4.3: + optional: true package-manager-detector@1.3.0: {} - path-to-regexp@6.3.0: {} + path-to-regexp@6.3.0: + optional: true pathe@2.0.3: {} @@ -2171,12 +2198,15 @@ snapshots: psl@1.15.0: dependencies: punycode: 2.3.1 + optional: true - punycode@2.3.1: {} + punycode@2.3.1: + optional: true quansync@0.2.10: {} - querystringify@2.2.0: {} + querystringify@2.2.0: + optional: true quick-format-unescaped@4.0.4: {} @@ -2189,9 +2219,11 @@ snapshots: real-require@0.2.0: {} - require-directory@2.1.1: {} + require-directory@2.1.1: + optional: true - requires-port@1.0.0: {} + requires-port@1.0.0: + optional: true resolve-pkg-maps@1.0.0: {} @@ -2289,7 +2321,8 @@ snapshots: siginfo@2.0.0: {} - signal-exit@4.1.0: {} + signal-exit@4.1.0: + optional: true simple-swizzle@0.2.2: dependencies: @@ -2305,21 +2338,25 @@ snapshots: stackback@0.0.2: {} - statuses@2.0.2: {} + statuses@2.0.2: + optional: true std-env@3.9.0: {} - strict-event-emitter@0.5.1: {} + strict-event-emitter@0.5.1: + optional: true string-width@4.2.3: dependencies: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + optional: true strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 + optional: true strip-literal@3.0.0: dependencies: @@ -2352,6 +2389,7 @@ snapshots: punycode: 2.3.1 universalify: 0.2.0 url-parse: 1.5.10 + optional: true tsdown@0.11.13(typescript@5.8.3): dependencies: @@ -2387,9 +2425,11 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - type-fest@0.21.3: {} + type-fest@0.21.3: + optional: true - type-fest@4.41.0: {} + type-fest@4.41.0: + optional: true typescript@5.8.3: {} @@ -2402,12 +2442,14 @@ snapshots: undici-types@6.21.0: {} - universalify@0.2.0: {} + universalify@0.2.0: + optional: true url-parse@1.5.10: dependencies: querystringify: 2.2.0 requires-port: 1.0.0 + optional: true vite-node@3.2.3(@types/node@22.15.31)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0): dependencies: @@ -2502,20 +2544,24 @@ snapshots: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 + optional: true wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 + optional: true ws@8.18.2: {} - y18n@5.0.8: {} + y18n@5.0.8: + optional: true yaml@2.8.0: {} - yargs-parser@21.1.1: {} + yargs-parser@21.1.1: + optional: true yargs@17.7.2: dependencies: @@ -2526,5 +2572,7 @@ snapshots: string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 21.1.1 + optional: true - yoctocolors-cjs@2.1.2: {} + yoctocolors-cjs@2.1.2: + optional: true diff --git a/libs/computer/typescript/src/interface/base.ts b/libs/computer/typescript/src/interface/base.ts index 169cf4b0..6455fb29 100644 --- a/libs/computer/typescript/src/interface/base.ts +++ b/libs/computer/typescript/src/interface/base.ts @@ -39,6 +39,7 @@ export abstract class BaseComputerInterface { protected ws: WebSocket; protected apiKey?: string; protected vmName?: string; + protected secure?: boolean; protected logger = pino({ name: "interface-base" }); @@ -47,13 +48,15 @@ export abstract class BaseComputerInterface { username = "lume", password = "lume", apiKey?: string, - vmName?: string + vmName?: string, + secure?: boolean ) { this.ipAddress = ipAddress; this.username = username; this.password = password; this.apiKey = apiKey; this.vmName = vmName; + this.secure = secure; // Initialize WebSocket with headers if needed const headers: { [key: string]: string } = {}; @@ -71,9 +74,15 @@ export abstract class BaseComputerInterface { * Subclasses can override this to customize the URI. */ protected get wsUri(): string { - // Use secure WebSocket for cloud provider with API key - const protocol = this.apiKey ? "wss" : "ws"; - const port = this.apiKey ? "8443" : "8000"; + const protocol = this.secure ? "wss" : "ws"; + + // Check if ipAddress already includes a port + if (this.ipAddress.includes(":")) { + return `${protocol}://${this.ipAddress}/ws`; + } + + // Otherwise, append the default port + const port = this.secure ? "8443" : "8000"; return `${protocol}://${this.ipAddress}:${port}/ws`; } diff --git a/libs/computer/typescript/tests/interface/integration.test.ts b/libs/computer/typescript/tests/interface/integration.test.ts deleted file mode 100644 index b0867ca7..00000000 --- a/libs/computer/typescript/tests/interface/integration.test.ts +++ /dev/null @@ -1,447 +0,0 @@ -import { - describe, - expect, - it, - beforeEach, - afterEach, - vi, - beforeAll, - afterAll, -} from "vitest"; -import { InterfaceFactory } from "../../src/interface/factory.ts"; -import { OSType } from "../../src/types.ts"; -import { ws } from "msw"; -import { setupServer } from "msw/node"; - -describe("Interface Integration Tests", () => { - const testIp = "192.168.1.100"; - const testPort = 8000; - - // Create WebSocket server - const server = setupServer(); - - beforeAll(() => { - server.listen({ onUnhandledRequest: "error" }); - }); - - afterAll(() => { - server.close(); - }); - - beforeEach(() => { - // Reset handlers for each test - server.resetHandlers(); - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - describe("Cross-platform interface creation", () => { - it("should create correct interface for each OS type", async () => { - const osTypes = [OSType.MACOS, OSType.LINUX, OSType.WINDOWS]; - const interfaces: Array<{ - os: OSType; - interface: ReturnType; - }> = []; - - // Create interfaces for each OS - for (const os of osTypes) { - const interface_ = InterfaceFactory.createInterfaceForOS(os, testIp); - interfaces.push({ os, interface: interface_ }); - } - - // Verify each interface is created correctly - expect(interfaces).toHaveLength(3); - for (const { os, interface: iface } of interfaces) { - expect(iface).toBeDefined(); - // Check that the interface name contains the OS type in some form - const osName = os.toLowerCase(); - expect(iface.constructor.name.toLowerCase()).toContain(osName); - } - }); - - it("should handle multiple interfaces with different IPs", async () => { - const ips = ["192.168.1.100", "192.168.1.101", "192.168.1.102"]; - const interfaces = ips.map((ip) => - InterfaceFactory.createInterfaceForOS(OSType.MACOS, ip) - ); - - // Set up WebSocket handlers for each IP - for (const ip of ips) { - const wsLink = ws.link(`ws://${ip}:${testPort}/ws`); - server.use( - wsLink.addEventListener("connection", ({ client }) => { - client.addEventListener("message", () => { - // Echo back success response - client.send(JSON.stringify({ success: true })); - }); - }) - ); - } - - // Connect all interfaces - await Promise.all(interfaces.map((iface) => iface.connect())); - - // Verify all are connected - for (const iface of interfaces) { - expect(iface.isConnected()).toBe(true); - } - - // Clean up - for (const iface of interfaces) { - iface.disconnect(); - } - }); - }); - - describe("Connection management", () => { - it("should handle connection lifecycle", async () => { - // Set up WebSocket handler - const wsLink = ws.link(`ws://${testIp}:${testPort}/ws`); - server.use( - wsLink.addEventListener("connection", ({ client }) => { - client.addEventListener("message", () => { - // Echo back success response - client.send(JSON.stringify({ success: true })); - }); - }) - ); - - const interface_ = InterfaceFactory.createInterfaceForOS( - OSType.MACOS, - testIp - ); - - // Initially not connected - expect(interface_.isConnected()).toBe(false); - - // Connect - await interface_.connect(); - expect(interface_.isConnected()).toBe(true); - - // Disconnect - interface_.disconnect(); - - // Wait a tick for the close to process - await new Promise((resolve) => process.nextTick(resolve)); - expect(interface_.isConnected()).toBe(false); - }); - - it("should handle connection errors gracefully", async () => { - // Don't register a handler - connection will succeed but no responses - const interface_ = InterfaceFactory.createInterfaceForOS( - OSType.MACOS, - "192.0.2.1" // TEST-NET-1 address - ); - - // Should connect (WebSocket mock always connects) - await interface_.connect(); - expect(interface_.isConnected()).toBe(true); - - interface_.disconnect(); - }); - - it("should handle secure connections", async () => { - const secureIp = "192.0.2.1"; - const securePort = 8443; - - // Register handler for secure connection - const wsLink = ws.link(`wss://${secureIp}:${securePort}/ws`); - server.use( - wsLink.addEventListener("connection", ({ client }) => { - client.addEventListener("message", () => { - // Echo back success response - client.send(JSON.stringify({ success: true })); - }); - }) - ); - - const interface_ = InterfaceFactory.createInterfaceForOS( - OSType.MACOS, - secureIp, - "testuser", - "testpass" - ); - - await interface_.connect(); - expect(interface_.isConnected()).toBe(true); - - interface_.disconnect(); - }); - }); - - describe("Performance and concurrency", () => { - it("should handle rapid command sequences", async () => { - const receivedCommands: string[] = []; - - // Set up WebSocket handler that tracks commands - const wsLink = ws.link(`ws://${testIp}:${testPort}/ws`); - server.use( - wsLink.addEventListener("connection", ({ client }) => { - client.addEventListener("message", (event) => { - const data = JSON.parse(event.data as string); - receivedCommands.push(data.action); - // Send response with command index - client.send( - JSON.stringify({ - success: true, - data: `Response for ${data.action}`, - }) - ); - }); - }) - ); - - const interface_ = InterfaceFactory.createInterfaceForOS( - OSType.MACOS, - testIp - ); - await interface_.connect(); - - // Send multiple commands rapidly - const commands = ["left_click", "right_click", "double_click"]; - const promises = commands.map((cmd) => { - switch (cmd) { - case "left_click": - return interface_.leftClick(100, 200); - case "right_click": - return interface_.rightClick(150, 250); - case "double_click": - return interface_.doubleClick(200, 300); - } - }); - - await Promise.all(promises); - - // Verify all commands were received - expect(receivedCommands).toHaveLength(3); - expect(receivedCommands).toContain("left_click"); - expect(receivedCommands).toContain("right_click"); - expect(receivedCommands).toContain("double_click"); - - interface_.disconnect(); - }); - - it("should maintain command order with locking", async () => { - const receivedCommands: Array<{ action: string; index: number }> = []; - - // Set up WebSocket handler that tracks commands with delay - const wsLink = ws.link(`ws://${testIp}:${testPort}/ws`); - server.use( - wsLink.addEventListener("connection", ({ client }) => { - client.addEventListener("message", async (event) => { - // Add delay to simulate processing - await new Promise((resolve) => setTimeout(resolve, 10)); - - const data = JSON.parse(event.data as string); - receivedCommands.push({ - action: data.action, - index: data.index, - }); - - client.send(JSON.stringify({ success: true })); - }); - }) - ); - - const interface_ = InterfaceFactory.createInterfaceForOS( - OSType.MACOS, - testIp - ); - await interface_.connect(); - - // Helper to send command with index - async function sendCommandWithIndex(action: string, index: number) { - await interface_.sendCommand({ action, index }); - } - - // Send commands in sequence - await sendCommandWithIndex("command1", 0); - await sendCommandWithIndex("command2", 1); - await sendCommandWithIndex("command3", 2); - - // Wait for all commands to be processed - await new Promise((resolve) => setTimeout(resolve, 50)); - - // Verify commands were received in order - expect(receivedCommands).toHaveLength(3); - expect(receivedCommands[0]).toEqual({ action: "command1", index: 0 }); - expect(receivedCommands[1]).toEqual({ action: "command2", index: 1 }); - expect(receivedCommands[2]).toEqual({ action: "command3", index: 2 }); - - interface_.disconnect(); - }); - }); - - describe("Error handling", () => { - it("should handle command failures", async () => { - // Set up WebSocket handler that returns errors - const wsLink = ws.link(`ws://${testIp}:${testPort}/ws`); - server.use( - wsLink.addEventListener("connection", ({ client }) => { - client.addEventListener("message", (event) => { - const data = JSON.parse(event.data as string); - - if (data.action === "fail_command") { - client.send( - JSON.stringify({ - success: false, - error: "Command failed", - }) - ); - } else { - client.send(JSON.stringify({ success: true })); - } - }); - }) - ); - - const interface_ = InterfaceFactory.createInterfaceForOS( - OSType.MACOS, - testIp - ); - await interface_.connect(); - - // Send a failing command - await expect( - interface_.sendCommand({ action: "fail_command" }) - ).rejects.toThrow("Command failed"); - - // Verify interface is still connected - expect(interface_.isConnected()).toBe(true); - - // Send a successful command - const result = await interface_.sendCommand({ - action: "success_command", - }); - expect(result.success).toBe(true); - - interface_.disconnect(); - }); - - it("should handle disconnection during command", async () => { - // Set up WebSocket handler that captures WebSocket instance - const wsLink = ws.link(`ws://${testIp}:${testPort}/ws`); - server.use( - wsLink.addEventListener("connection", ({ client }) => { - client.addEventListener("message", async () => { - // Simulate disconnection during command processing - await new Promise((resolve) => setTimeout(resolve, 10)); - client.close(); - }); - }) - ); - - const interface_ = InterfaceFactory.createInterfaceForOS( - OSType.MACOS, - testIp - ); - await interface_.connect(); - - // Send command that will trigger disconnection - await expect( - interface_.sendCommand({ action: "disconnect_me" }) - ).rejects.toThrow(); - - // Wait for close to process - await new Promise((resolve) => setTimeout(resolve, 20)); - - // Verify interface is disconnected - expect(interface_.isConnected()).toBe(false); - }); - }); - - describe("Feature-specific tests", () => { - it("should handle screenshot commands", async () => { - // Set up WebSocket handler - const wsLink = ws.link(`ws://${testIp}:${testPort}/ws`); - server.use( - wsLink.addEventListener("connection", ({ client }) => { - client.addEventListener("message", () => { - // Echo back success response with screenshot data - client.send(JSON.stringify({ - success: true, - data: "base64encodedimage" - })); - }); - }) - ); - - const interface_ = InterfaceFactory.createInterfaceForOS( - OSType.MACOS, - testIp - ); - await interface_.connect(); - - const screenshot = await interface_.screenshot(); - expect(screenshot).toBeInstanceOf(Buffer); - expect(screenshot.toString("base64")).toBe("base64encodedimage"); - - interface_.disconnect(); - }); - - it("should handle screen size queries", async () => { - // Set up WebSocket handler - const wsLink = ws.link(`ws://${testIp}:${testPort}/ws`); - server.use( - wsLink.addEventListener("connection", ({ client }) => { - client.addEventListener("message", () => { - // Echo back success response with screen size - client.send(JSON.stringify({ - success: true, - data: { width: 1920, height: 1080 } - })); - }); - }) - ); - - const interface_ = InterfaceFactory.createInterfaceForOS( - OSType.LINUX, - testIp - ); - await interface_.connect(); - - const screenSize = await interface_.getScreenSize(); - expect(screenSize).toEqual({ width: 1920, height: 1080 }); - - interface_.disconnect(); - }); - - it("should handle file operations", async () => { - // Set up WebSocket handler - const wsLink = ws.link(`ws://${testIp}:${testPort}/ws`); - server.use( - wsLink.addEventListener("connection", ({ client }) => { - client.addEventListener("message", () => { - // Echo back success response with file data - client.send(JSON.stringify({ - success: true, - data: "file content" - })); - }); - }) - ); - - const interface_ = InterfaceFactory.createInterfaceForOS( - OSType.WINDOWS, - testIp - ); - await interface_.connect(); - - // Test file exists - const exists = await interface_.fileExists("/test/file.txt"); - expect(exists).toBe(true); - - // Test read text - const content = await interface_.readText("/test/file.txt"); - expect(content).toBe("file content"); - - // Test list directory - const files = await interface_.listDir("/test"); - expect(files).toEqual(["file1.txt", "file2.txt", "dir1"]); - - interface_.disconnect(); - }); - }); -}); diff --git a/libs/computer/typescript/tests/interface/linux.test.ts b/libs/computer/typescript/tests/interface/linux.test.ts index d5d8532b..c82627c7 100644 --- a/libs/computer/typescript/tests/interface/linux.test.ts +++ b/libs/computer/typescript/tests/interface/linux.test.ts @@ -4,7 +4,7 @@ import { MacOSComputerInterface } from "../../src/interface/macos.ts"; describe("LinuxComputerInterface", () => { const testParams = { - ipAddress: "192.0.2.1", // TEST-NET-1 address (RFC 5737) - guaranteed not to be routable + ipAddress: "test.cua.com", // TEST-NET-1 address (RFC 5737) - guaranteed not to be routable username: "testuser", password: "testpass", apiKey: "test-api-key", diff --git a/libs/computer/typescript/tests/interface/macos.test.ts b/libs/computer/typescript/tests/interface/macos.test.ts index fb7e4b08..78d2d9d3 100644 --- a/libs/computer/typescript/tests/interface/macos.test.ts +++ b/libs/computer/typescript/tests/interface/macos.test.ts @@ -1,36 +1,139 @@ -import { - describe, - expect, - it, - beforeEach, - afterEach, -} from "vitest"; +import { describe, expect, it, beforeEach, afterEach } from "vitest"; import { MacOSComputerInterface } from "../../src/interface/macos.ts"; -// Import the setup.ts which already has MSW configured -import "../setup.ts"; +import { WebSocketServer, WebSocket } from "ws"; describe("MacOSComputerInterface", () => { // Define test parameters const testParams = { - ipAddress: "192.0.2.1", // TEST-NET-1 address (RFC 5737) - guaranteed not to be routable + ipAddress: "localhost", username: "testuser", password: "testpass", apiKey: "test-api-key", vmName: "test-vm", }; - // Track received messages for verification - // biome-ignore lint/suspicious/noExplicitAny: - let receivedMessages: any[] = []; + // WebSocket server mock + let wss: WebSocketServer; + let serverPort: number; + let connectedClients: WebSocket[] = []; - beforeEach(() => { - // Clear received messages before each test + // Track received messages for verification + interface ReceivedMessage { + action: string; + [key: string]: unknown; + } + let receivedMessages: ReceivedMessage[] = []; + + // Set up WebSocket server before all tests + beforeEach(async () => { receivedMessages = []; + connectedClients = []; + + // Create WebSocket server on a random available port + wss = new WebSocketServer({ port: 0 }); + serverPort = (wss.address() as { port: number }).port; + + // Update test params with the actual server address + testParams.ipAddress = `localhost:${serverPort}`; + + // Handle WebSocket connections + wss.on("connection", (ws, req) => { + connectedClients.push(ws); + + // Verify authentication headers + const apiKey = req.headers["x-api-key"]; + const vmName = req.headers["x-vm-name"]; + + if (apiKey !== testParams.apiKey || vmName !== testParams.vmName) { + ws.close(1008, "Unauthorized"); + return; + } + + // Handle incoming messages + ws.on("message", (data) => { + try { + const message = JSON.parse(data.toString()); + receivedMessages.push(message); + + // Send appropriate responses based on action + switch (message.action) { + case "screenshot": + ws.send(JSON.stringify({ + data: Buffer.from("fake-screenshot-data").toString("base64") + })); + break; + case "get_screen_size": + ws.send(JSON.stringify({ data: { width: 1920, height: 1080 } })); + break; + case "get_cursor_position": + ws.send(JSON.stringify({ data: { x: 100, y: 200 } })); + break; + case "copy_to_clipboard": + ws.send(JSON.stringify({ data: "clipboard content" })); + break; + case "file_exists": + ws.send(JSON.stringify({ data: true })); + break; + case "directory_exists": + ws.send(JSON.stringify({ data: true })); + break; + case "list_dir": + ws.send(JSON.stringify({ data: ["file1.txt", "file2.txt"] })); + break; + case "read_text": + ws.send(JSON.stringify({ data: "file content" })); + break; + case "read_bytes": + ws.send(JSON.stringify({ + data: Buffer.from("binary content").toString("base64") + })); + break; + case "run_command": + ws.send(JSON.stringify({ data: { stdout: "command output", stderr: "" } })); + break; + case "get_accessibility_tree": + ws.send(JSON.stringify({ + data: { + role: "window", + title: "Test Window", + bounds: { x: 0, y: 0, width: 1920, height: 1080 }, + children: [] + } + })); + break; + case "to_screen_coordinates": + case "to_screenshot_coordinates": + ws.send(JSON.stringify({ data: [message.x || 0, message.y || 0] })); + break; + default: + // For all other actions, just send success + ws.send(JSON.stringify({ success: true })); + break; + } + } catch (error) { + ws.send(JSON.stringify({ error: (error as Error).message })); + } + }); + + ws.on("error", (error) => { + console.error("WebSocket error:", error); + }); + }); }); - afterEach(() => { - // Clear any state after each test - receivedMessages = []; + // Clean up WebSocket server after each test + afterEach(async () => { + // Close all connected clients + for (const client of connectedClients) { + if (client.readyState === WebSocket.OPEN) { + client.close(); + } + } + + // Close the server + await new Promise((resolve) => { + wss.close(() => resolve()); + }); }); describe("Connection Management", () => { @@ -40,16 +143,47 @@ describe("MacOSComputerInterface", () => { testParams.username, testParams.password, testParams.apiKey, - testParams.vmName + testParams.vmName, + false ); await macosInterface.connect(); // Verify the interface is connected expect(macosInterface.isConnected()).toBe(true); + expect(connectedClients.length).toBe(1); await macosInterface.disconnect(); }); + + it("should handle connection without API key", async () => { + // Create a separate server that doesn't check auth + const noAuthWss = new WebSocketServer({ port: 0 }); + const noAuthPort = (noAuthWss.address() as { port: number }).port; + + noAuthWss.on("connection", (ws) => { + ws.on("message", () => { + ws.send(JSON.stringify({ success: true })); + }); + }); + + const macosInterface = new MacOSComputerInterface( + `localhost:${noAuthPort}`, + testParams.username, + testParams.password, + undefined, + undefined, + false + ); + + await macosInterface.connect(); + expect(macosInterface.isConnected()).toBe(true); + + await macosInterface.disconnect(); + await new Promise((resolve) => { + noAuthWss.close(() => resolve()); + }); + }); }); describe("Mouse Actions", () => { @@ -61,106 +195,109 @@ describe("MacOSComputerInterface", () => { testParams.username, testParams.password, testParams.apiKey, - testParams.vmName + testParams.vmName, + false ); - // Remove initialize() call - connection happens on first command - await new Promise((resolve) => setTimeout(resolve, 100)); + await macosInterface.connect(); }); afterEach(async () => { - await macosInterface.disconnect(); + if (macosInterface) { + await macosInterface.disconnect(); + } }); it("should send mouse_down command", async () => { await macosInterface.mouseDown(100, 200, "left"); - - expect(receivedMessages).toHaveLength(1); - expect(receivedMessages[0]).toEqual({ + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ action: "mouse_down", x: 100, y: 200, - button: "left", + button: "left" }); }); it("should send mouse_up command", async () => { await macosInterface.mouseUp(100, 200, "right"); - - expect(receivedMessages).toHaveLength(1); - expect(receivedMessages[0]).toEqual({ + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ action: "mouse_up", x: 100, y: 200, - button: "right", + button: "right" }); }); it("should send left_click command", async () => { await macosInterface.leftClick(150, 250); - - expect(receivedMessages).toHaveLength(1); - expect(receivedMessages[0]).toEqual({ + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ action: "left_click", x: 150, - y: 250, + y: 250 }); }); it("should send right_click command", async () => { - await macosInterface.rightClick(150, 250); - - expect(receivedMessages).toHaveLength(1); - expect(receivedMessages[0]).toEqual({ + await macosInterface.rightClick(200, 300); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ action: "right_click", - x: 150, - y: 250, + x: 200, + y: 300 }); }); it("should send double_click command", async () => { - await macosInterface.doubleClick(150, 250); - - expect(receivedMessages).toHaveLength(1); - expect(receivedMessages[0]).toEqual({ + await macosInterface.doubleClick(250, 350); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ action: "double_click", - x: 150, - y: 250, + x: 250, + y: 350 }); }); it("should send move_cursor command", async () => { await macosInterface.moveCursor(300, 400); - - expect(receivedMessages).toHaveLength(1); - expect(receivedMessages[0]).toEqual({ + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ action: "move_cursor", x: 300, - y: 400, + y: 400 }); }); it("should send drag_to command", async () => { - await macosInterface.dragTo(500, 600, "left", 1.5); - - expect(receivedMessages).toHaveLength(1); - expect(receivedMessages[0]).toEqual({ + await macosInterface.dragTo(400, 500, "left", 1.5); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ action: "drag_to", - x: 500, - y: 600, + x: 400, + y: 500, button: "left", - duration: 1.5, + duration: 1.5 }); }); - it("should send scroll command", async () => { - await macosInterface.scroll(0, 10); - - expect(receivedMessages).toHaveLength(1); - expect(receivedMessages[0]).toEqual({ - action: "scroll", - x: 0, - y: 10, - clicks: 5, + it("should send drag command with path", async () => { + const path: Array<[number, number]> = [[100, 100], [200, 200], [300, 300]]; + await macosInterface.drag(path, "middle", 2.0); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ + action: "drag", + path: path, + button: "middle", + duration: 2.0 }); }); }); @@ -174,63 +311,118 @@ describe("MacOSComputerInterface", () => { testParams.username, testParams.password, testParams.apiKey, - testParams.vmName + testParams.vmName, + false ); - // Remove initialize() call - await new Promise((resolve) => setTimeout(resolve, 100)); + await macosInterface.connect(); }); afterEach(async () => { - await macosInterface.disconnect(); + if (macosInterface) { + await macosInterface.disconnect(); + } }); it("should send key_down command", async () => { await macosInterface.keyDown("a"); - - expect(receivedMessages).toHaveLength(1); - expect(receivedMessages[0]).toEqual({ + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ action: "key_down", - key: "a", + key: "a" }); }); it("should send key_up command", async () => { - await macosInterface.keyUp("a"); - - expect(receivedMessages).toHaveLength(1); - expect(receivedMessages[0]).toEqual({ + await macosInterface.keyUp("b"); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ action: "key_up", - key: "a", - }); - }); - - it("should send key_press command", async () => { - await macosInterface.keyDown("enter"); - - expect(receivedMessages).toHaveLength(1); - expect(receivedMessages[0]).toEqual({ - action: "key_press", - key: "enter", + key: "b" }); }); it("should send type_text command", async () => { await macosInterface.typeText("Hello, World!"); - - expect(receivedMessages).toHaveLength(1); - expect(receivedMessages[0]).toEqual({ + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ action: "type_text", - text: "Hello, World!", + text: "Hello, World!" + }); + }); + + it("should send press_key command", async () => { + await macosInterface.pressKey("enter"); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ + action: "press_key", + key: "enter" }); }); it("should send hotkey command", async () => { await macosInterface.hotkey("cmd", "c"); - - expect(receivedMessages).toHaveLength(1); - expect(receivedMessages[0]).toEqual({ + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ action: "hotkey", - keys: ["cmd", "c"], + keys: ["cmd", "c"] + }); + }); + }); + + describe("Scrolling Actions", () => { + let macosInterface: MacOSComputerInterface; + + beforeEach(async () => { + macosInterface = new MacOSComputerInterface( + testParams.ipAddress, + testParams.username, + testParams.password, + testParams.apiKey, + testParams.vmName, + false + ); + await macosInterface.connect(); + }); + + afterEach(async () => { + if (macosInterface) { + await macosInterface.disconnect(); + } + }); + + it("should send scroll command", async () => { + await macosInterface.scroll(10, -5); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ + action: "scroll", + x: 10, + y: -5 + }); + }); + + it("should send scroll_down command", async () => { + await macosInterface.scrollDown(3); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ + action: "scroll_down", + clicks: 3 + }); + }); + + it("should send scroll_up command", async () => { + await macosInterface.scrollUp(2); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ + action: "scroll_up", + clicks: 2 }); }); }); @@ -244,62 +436,54 @@ describe("MacOSComputerInterface", () => { testParams.username, testParams.password, testParams.apiKey, - testParams.vmName + testParams.vmName, + false ); - // Remove initialize() call - await new Promise((resolve) => setTimeout(resolve, 100)); + await macosInterface.connect(); }); afterEach(async () => { - await macosInterface.disconnect(); + if (macosInterface) { + await macosInterface.disconnect(); + } }); it("should get screenshot", async () => { const screenshot = await macosInterface.screenshot(); - - expect(receivedMessages).toHaveLength(1); - expect(receivedMessages[0]).toEqual({ - action: "screenshot", + + expect(screenshot).toBeInstanceOf(Buffer); + expect(screenshot.toString()).toBe("fake-screenshot-data"); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ + action: "screenshot" }); - expect(screenshot).toBe("base64encodedimage"); }); it("should get screen size", async () => { - const screenSize = await macosInterface.getScreenSize(); - - expect(receivedMessages).toHaveLength(1); - expect(receivedMessages[0]).toEqual({ - action: "get_screen_size", + const size = await macosInterface.getScreenSize(); + + expect(size).toEqual({ width: 1920, height: 1080 }); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ + action: "get_screen_size" }); - expect(screenSize).toEqual({ width: 1920, height: 1080 }); }); it("should get cursor position", async () => { const position = await macosInterface.getCursorPosition(); - - expect(receivedMessages).toHaveLength(1); - expect(receivedMessages[0]).toEqual({ - action: "get_cursor_position", - }); + expect(position).toEqual({ x: 100, y: 200 }); - }); - - it("should get accessibility tree", async () => { - const tree = await macosInterface.getAccessibilityTree(); - - expect(receivedMessages).toHaveLength(1); - expect(receivedMessages[0]).toEqual({ - action: "get_accessibility_tree", - }); - expect(tree).toEqual({ - role: "window", - title: "Test Window", - children: [], + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ + action: "get_cursor_position" }); }); }); - describe("System Actions", () => { + describe("Clipboard Actions", () => { let macosInterface: MacOSComputerInterface; beforeEach(async () => { @@ -308,28 +492,251 @@ describe("MacOSComputerInterface", () => { testParams.username, testParams.password, testParams.apiKey, - testParams.vmName + testParams.vmName, + false ); - // Remove initialize() call - await new Promise((resolve) => setTimeout(resolve, 100)); + await macosInterface.connect(); }); afterEach(async () => { - await macosInterface.disconnect(); + if (macosInterface) { + await macosInterface.disconnect(); + } + }); + + it("should copy to clipboard", async () => { + const text = await macosInterface.copyToClipboard(); + + expect(text).toBe("clipboard content"); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ + action: "copy_to_clipboard" + }); + }); + + it("should set clipboard", async () => { + await macosInterface.setClipboard("new clipboard text"); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ + action: "set_clipboard", + text: "new clipboard text" + }); + }); + }); + + describe("File System Actions", () => { + let macosInterface: MacOSComputerInterface; + + beforeEach(async () => { + macosInterface = new MacOSComputerInterface( + testParams.ipAddress, + testParams.username, + testParams.password, + testParams.apiKey, + testParams.vmName, + false + ); + await macosInterface.connect(); + }); + + afterEach(async () => { + if (macosInterface) { + await macosInterface.disconnect(); + } + }); + + it("should check file exists", async () => { + const exists = await macosInterface.fileExists("/path/to/file"); + + expect(exists).toBe(true); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ + action: "file_exists", + path: "/path/to/file" + }); + }); + + it("should check directory exists", async () => { + const exists = await macosInterface.directoryExists("/path/to/dir"); + + expect(exists).toBe(true); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ + action: "directory_exists", + path: "/path/to/dir" + }); + }); + + it("should list directory", async () => { + const files = await macosInterface.listDir("/path/to/dir"); + + expect(files).toEqual(["file1.txt", "file2.txt"]); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ + action: "list_dir", + path: "/path/to/dir" + }); + }); + + it("should read text file", async () => { + const content = await macosInterface.readText("/path/to/file.txt"); + + expect(content).toBe("file content"); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ + action: "read_text", + path: "/path/to/file.txt" + }); + }); + + it("should write text file", async () => { + await macosInterface.writeText("/path/to/file.txt", "new content"); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ + action: "write_text", + path: "/path/to/file.txt", + content: "new content" + }); + }); + + it("should read binary file", async () => { + const content = await macosInterface.readBytes("/path/to/file.bin"); + + expect(content).toBeInstanceOf(Buffer); + expect(content.toString()).toBe("binary content"); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ + action: "read_bytes", + path: "/path/to/file.bin" + }); + }); + + it("should write binary file", async () => { + const buffer = Buffer.from("binary data"); + await macosInterface.writeBytes("/path/to/file.bin", buffer); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ + action: "write_bytes", + path: "/path/to/file.bin", + content: buffer.toString("base64") + }); + }); + + it("should delete file", async () => { + await macosInterface.deleteFile("/path/to/file"); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ + action: "delete_file", + path: "/path/to/file" + }); + }); + + it("should create directory", async () => { + await macosInterface.createDir("/path/to/new/dir"); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ + action: "create_dir", + path: "/path/to/new/dir" + }); + }); + + it("should delete directory", async () => { + await macosInterface.deleteDir("/path/to/dir"); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ + action: "delete_dir", + path: "/path/to/dir" + }); }); it("should run command", async () => { - const result = await macosInterface.runCommand("ls -la"); - - expect(receivedMessages).toHaveLength(1); - expect(receivedMessages[0]).toEqual({ + const [stdout, stderr] = await macosInterface.runCommand("ls -la"); + + expect(stdout).toBe("command output"); + expect(stderr).toBe(""); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ action: "run_command", - command: "ls -la", + command: "ls -la" }); - expect(result).toEqual({ - stdout: "command output", - stderr: "", - returncode: 0, + }); + }); + + describe("Accessibility Actions", () => { + let macosInterface: MacOSComputerInterface; + + beforeEach(async () => { + macosInterface = new MacOSComputerInterface( + testParams.ipAddress, + testParams.username, + testParams.password, + testParams.apiKey, + testParams.vmName, + false + ); + await macosInterface.connect(); + }); + + afterEach(async () => { + if (macosInterface) { + await macosInterface.disconnect(); + } + }); + + it("should get accessibility tree", async () => { + const tree = await macosInterface.getAccessibilityTree(); + + expect(tree).toEqual({ + role: "window", + title: "Test Window", + bounds: { x: 0, y: 0, width: 1920, height: 1080 }, + children: [] + }); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ + action: "get_accessibility_tree" + }); + }); + + it("should convert to screen coordinates", async () => { + const [x, y] = await macosInterface.toScreenCoordinates(100, 200); + + expect(x).toBe(100); + expect(y).toBe(200); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ + action: "to_screen_coordinates", + x: 100, + y: 200 + }); + }); + + it("should convert to screenshot coordinates", async () => { + const [x, y] = await macosInterface.toScreenshotCoordinates(300, 400); + + expect(x).toBe(300); + expect(y).toBe(400); + + const lastMessage = receivedMessages[receivedMessages.length - 1]; + expect(lastMessage).toEqual({ + action: "to_screenshot_coordinates", + x: 300, + y: 400 }); }); }); @@ -338,107 +745,89 @@ describe("MacOSComputerInterface", () => { it("should handle WebSocket connection errors", async () => { // Use a valid but unreachable IP to avoid DNS errors const macosInterface = new MacOSComputerInterface( - testParams.ipAddress, + "localhost:9999", testParams.username, testParams.password, testParams.apiKey, - testParams.vmName + testParams.vmName, + false ); - // Try to send a command - should fail with connection error - await expect(macosInterface.screenshot()).rejects.toThrow(); - - await macosInterface.disconnect(); + // Connection should fail + await expect(macosInterface.connect()).rejects.toThrow(); }); - it("should handle server error responses", async () => { - // Override the handler to send error response - // server.use( - // chat.addEventListener("connection", ({ client, server }) => { - // client.addEventListener("message", () => { - // server.send( - // JSON.stringify({ - // success: false, - // error: "Command failed", - // }) - // ); - // }); - // }) - // ); + it("should handle command errors", async () => { + // Create a server that returns errors + const errorWss = new WebSocketServer({ port: 0 }); + const errorPort = (errorWss.address() as { port: number }).port; + errorWss.on("connection", (ws) => { + ws.on("message", () => { + ws.send(JSON.stringify({ error: "Command failed" })); + }); + }); + + const macosInterface = new MacOSComputerInterface( + `localhost:${errorPort}`, + testParams.username, + testParams.password, + testParams.apiKey, + testParams.vmName, + false + ); + + await macosInterface.connect(); + + // Command should throw error + await expect(macosInterface.leftClick(100, 100)).rejects.toThrow("Command failed"); + + await macosInterface.disconnect(); + await new Promise((resolve) => { + errorWss.close(() => resolve()); + }); + }); + + it("should handle disconnection gracefully", async () => { const macosInterface = new MacOSComputerInterface( testParams.ipAddress, testParams.username, testParams.password, testParams.apiKey, - testParams.vmName + testParams.vmName, + false ); - // Remove initialize() call - await new Promise((resolve) => setTimeout(resolve, 100)); - await expect(macosInterface.screenshot()).rejects.toThrow( - "Command failed" - ); + await macosInterface.connect(); + expect(macosInterface.isConnected()).toBe(true); + + // Disconnect + macosInterface.disconnect(); + expect(macosInterface.isConnected()).toBe(false); + + // Should reconnect automatically on next command + await macosInterface.leftClick(100, 100); + expect(macosInterface.isConnected()).toBe(true); await macosInterface.disconnect(); }); - it("should handle closed connection", async () => { + it("should handle force close", async () => { const macosInterface = new MacOSComputerInterface( testParams.ipAddress, testParams.username, testParams.password, testParams.apiKey, - testParams.vmName + testParams.vmName, + false ); - // Send a command to trigger connection - await macosInterface.screenshot(); + await macosInterface.connect(); + expect(macosInterface.isConnected()).toBe(true); - // Close the interface - await macosInterface.disconnect(); - - // Try to use after closing - await expect(macosInterface.screenshot()).rejects.toThrow( - "Interface is closed" - ); - }); - }); - - describe("Command Locking", () => { - let macosInterface: MacOSComputerInterface; - - beforeEach(async () => { - macosInterface = new MacOSComputerInterface( - testParams.ipAddress, - testParams.username, - testParams.password, - testParams.apiKey, - testParams.vmName - ); - // Remove initialize() call - await new Promise((resolve) => setTimeout(resolve, 100)); - }); - - afterEach(async () => { - await macosInterface.disconnect(); - }); - - it("should serialize commands", async () => { - // Send multiple commands simultaneously - const promises = [ - macosInterface.leftClick(100, 100), - macosInterface.rightClick(200, 200), - macosInterface.typeText("test"), - ]; - - await Promise.all(promises); - - // Commands should be sent in order - expect(receivedMessages).toHaveLength(3); - expect(receivedMessages[0].action).toBe("left_click"); - expect(receivedMessages[1].action).toBe("right_click"); - expect(receivedMessages[2].action).toBe("type_text"); + // Force close + macosInterface.forceClose(); + expect(macosInterface.isConnected()).toBe(false); }); }); }); diff --git a/libs/computer/typescript/tests/setup.ts b/libs/computer/typescript/tests/setup.ts index 42b09201..7dd1ebc7 100644 --- a/libs/computer/typescript/tests/setup.ts +++ b/libs/computer/typescript/tests/setup.ts @@ -1,26 +1,7 @@ import { afterAll, afterEach, beforeAll } from "vitest"; -import { setupServer } from "msw/node"; -import { ws } from "msw"; -const chat = ws.link("wss://chat.example.com"); +beforeAll(() => {}); -const wsHandlers = [ - chat.addEventListener("connection", ({ client }) => { - client.addEventListener("message", (event) => { - console.log("Received message from client:", event.data); - // Echo the received message back to the client - client.send(`Server received: ${event.data}`); - }); - }), -]; +afterAll(() => {}); -const server = setupServer(...wsHandlers); - -// Start server before all tests -beforeAll(() => server.listen({ onUnhandledRequest: "error" })); - -// Close server after all tests -afterAll(() => server.close()); - -// Reset handlers after each test for test isolation -afterEach(() => server.resetHandlers()); +afterEach(() => {}); From 22a56c449147b1948dec733f22dabeedc39e39d6 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Thu, 19 Jun 2025 18:44:49 -0400 Subject: [PATCH 075/141] added compat matrix to readme --- COMPATIBILITY.md | 119 +++++++++++++++++++++++++++++++++++++++++++++++ README.md | 14 ++++-- 2 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 COMPATIBILITY.md diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md new file mode 100644 index 00000000..2ff8e5a6 --- /dev/null +++ b/COMPATIBILITY.md @@ -0,0 +1,119 @@ +# C/ua Compatibility Matrix + +## Table of Contents +- [Host OS Compatibility](#host-os-compatibility) + - [macOS Host](#macos-host) + - [Ubuntu/Linux Host](#ubuntulinux-host) + - [Windows Host](#windows-host) +- [VM Emulation Support](#vm-emulation-support) +- [Installation Method Details](#installation-method-details) + +--- + +## Host OS Compatibility + +*This section shows compatibility based on your **host operating system** (the OS you're running C/ua on).* + +### macOS Host + +| Installation Method | Requirements | Lume | Cloud | Notes | +|-------------------|-------------|------|-------|-------| +| **playground-docker.sh** | Docker Desktop | ✅ Full | ✅ Full | Recommended for quick setup | +| **Dev Container** | VS Code/WindSurf + Docker | ✅ Full | ✅ Full | Best for development | +| **PyPI packages** | Python 3.12+ | ✅ Full | ✅ Full | Most flexible | + +**macOS Host Requirements:** +- macOS 15+ (Sequoia) for local VM support +- Apple Silicon (M1/M2/M3/M4) recommended for best performance +- Docker Desktop for containerized installations + +--- + +### Ubuntu/Linux Host + +| Installation Method | Requirements | Lume | Cloud | Notes | +|-------------------|-------------|------|-------|-------| +| **playground-docker.sh** | Docker Engine | ✅ Full | ✅ Full | Recommended for quick setup | +| **Dev Container** | VS Code/WindSurf + Docker | ✅ Full | ✅ Full | Best for development | +| **PyPI packages** | Python 3.12+ | ✅ Full | ✅ Full | Most flexible | + +**Ubuntu/Linux Host Requirements:** +- Ubuntu 20.04+ or equivalent Linux distribution +- Docker Engine or Docker Desktop +- Python 3.12+ for PyPI installation + +--- + +### Windows Host + +| Installation Method | Requirements | Lume | Winsandbox | Cloud | Notes | +|-------------------|-------------|------|------------|-------|-------| +| **playground-docker.sh** | Docker Desktop + WSL2 | ❌ Not supported | ❌ Not supported | ✅ Full | Requires WSL2 | +| **Dev Container** | VS Code/WindSurf + Docker + WSL2 | ❌ Not supported | ❌ Not supported | ✅ Full | Requires WSL2 | +| **PyPI packages** | Python 3.12+ | ❌ Not supported | ✅ Limited | ✅ Full | WSL for .sh scripts | + +**Windows Host Requirements:** +- Windows 10/11 with WSL2 enabled for shell script execution +- Docker Desktop with WSL2 backend +- Windows Sandbox feature enabled (for Winsandbox support) +- Python 3.12+ installed in WSL2 or Windows +- **Note**: Lume CLI is not available on Windows - use Cloud or Winsandbox providers + +--- + +## VM Emulation Support + +*This section shows which **virtual machine operating systems** each provider can emulate.* + +| Provider | macOS VM | Ubuntu/Linux VM | Windows VM | Notes | +|----------|----------|-----------------|------------|-------| +| **Lume** | ✅ Full support | ⚠️ Limited support | ⚠️ Limited support | macOS: native; Ubuntu/Linux/Windows: need custom image | +| **Cloud** | 🚧 Coming soon | ✅ Full support | 🚧 Coming soon | Currently Ubuntu only, macOS/Windows in development | +| **Winsandbox** | ❌ Not supported | ❌ Not supported | ✅ Windows only | Windows Sandbox environments only | + +### VM Emulation Details + +#### Lume VM Support +- **macOS VMs**: ✅ Full native support with official images +- **Ubuntu/Linux VMs**: ⚠️ Limited support - requires custom image creation +- **Windows VMs**: ⚠️ Limited support - requires custom image creation + +#### Cloud VM Support +- **Ubuntu/Linux VMs**: ✅ Full support with managed cloud instances +- **macOS VMs**: 🚧 Coming soon - in development +- **Windows VMs**: 🚧 Coming soon - in development + +#### Windows Sandbox VM Support +- **Windows VMs**: ✅ Full support for Windows Sandbox environments +- **macOS/Linux VMs**: ❌ Not supported - Windows Sandbox only runs Windows + +--- + +## Installation Method Details + +### playground-docker.sh +- **Containerized setup** using Docker +- Handles all dependencies automatically +- Requires Docker Desktop (Windows/macOS) or Docker Engine (Linux) +- **Windows note**: Must run in WSL2 environment + +### Dev Container +- **Development-focused** setup for contributors +- Integrates with VS Code and WindSurf IDEs +- Provides consistent development environment +- **Windows note**: Requires WSL2 backend for Docker + +### PyPI packages +- **Manual installation** via pip +- Most flexible installation method +- Allows custom configurations and integrations +- **Windows note**: Shell scripts require WSL2, but Python packages work natively + +--- + +## Legend + +- ✅ **Full support**: All features work natively without limitations +- ⚠️ **Partial support**: Requires additional setup (e.g., WSL2) or has limitations +- ❌ **Not supported**: Feature/provider combination is not available +- 🚧 **Coming soon**: Feature/provider combination is in development diff --git a/README.md b/README.md index 041de65e..fa4c2282 100644 --- a/README.md +++ b/README.md @@ -74,20 +74,26 @@ This repository includes a [Dev Container](./.devcontainer/README.md) configurat 2. **Open the repository in the Dev Container:** - Press `Ctrl+Shift+P` (or `Cmd+Shift+P` on macOS) - Select `Dev Containers: Clone Repository in Container Volume...` and paste the repository URL: `https://github.com/trycua/cua.git` (if not cloned) or `Dev Containers: Open Folder in Container...` (if already cloned). **Note**: On WindSurf, the post install hook might not run automatically. If so, run `/bin/bash .devcontainer/post-install.sh` manually. -3. **Run the Agent UI example:** Click ![Run Agent UI](https://github.com/user-attachments/assets/7a61ef34-4b22-4dab-9864-f86bf83e290b) +3. **Open the VS Code workspace:** Once the post-install.sh is done running, open the `.vscode/py.code-workspace` workspace and press "Open Workspace". +4. **Run the Agent UI example:** Click ![Run Agent UI](https://github.com/user-attachments/assets/7a61ef34-4b22-4dab-9864-f86bf83e290b) to start the Gradio UI. If prompted to install **debugpy (Python Debugger)** to enable remote debugging, select 'Yes' to proceed. -4. **Access the Gradio UI:** The Gradio UI will be available at `http://localhost:7860` and will automatically forward to your host machine. +5. **Access the Gradio UI:** The Gradio UI will be available at `http://localhost:7860` and will automatically forward to your host machine. --- -*How it works: Computer module provides secure desktops (Lume CLI locally, [C/ua Cloud Containers](https://trycua.com) remotely), Agent module provides local/API agents with OpenAI AgentResponse format and [trajectory tracing](https://trycua.com/trajectory-viewer).* -### Supported [Agent Loops](https://github.com/trycua/cua/blob/main/libs/agent/README.md#agent-loops) + +## Supported [Agent Loops](https://github.com/trycua/cua/blob/main/libs/agent/README.md#agent-loops) - [UITARS-1.5](https://github.com/trycua/cua/blob/main/libs/agent/README.md#agent-loops) - Run locally on Apple Silicon with MLX, or use cloud providers - [OpenAI CUA](https://github.com/trycua/cua/blob/main/libs/agent/README.md#agent-loops) - Use OpenAI's Computer-Use Preview model - [Anthropic CUA](https://github.com/trycua/cua/blob/main/libs/agent/README.md#agent-loops) - Use Anthropic's Computer-Use capabilities - [OmniParser-v2.0](https://github.com/trycua/cua/blob/main/libs/agent/README.md#agent-loops) - Control UI with [Set-of-Marks prompting](https://som-gpt4v.github.io/) using any vision model +## 🖥️ OS Compatibility +For detailed compatibility information including host OS support and VM emulation capabilities, see the [**Compatibility Matrix**](./COMPATIBILITY.md). + +
+
# 💻 Developer Guide From 0f64e2eeef909f479a86fbfe1169a5bbf9170128 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Thu, 19 Jun 2025 19:09:01 -0400 Subject: [PATCH 076/141] added model compat --- COMPATIBILITY.md | 53 +++++++++--------------------------------------- README.md | 21 +++++++++++++++---- 2 files changed, 27 insertions(+), 47 deletions(-) diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index 2ff8e5a6..a00d6e45 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -6,7 +6,7 @@ - [Ubuntu/Linux Host](#ubuntulinux-host) - [Windows Host](#windows-host) - [VM Emulation Support](#vm-emulation-support) -- [Installation Method Details](#installation-method-details) +- [Model Provider Compatibility](#model-provider-compatibility) --- @@ -71,49 +71,16 @@ | **Cloud** | 🚧 Coming soon | ✅ Full support | 🚧 Coming soon | Currently Ubuntu only, macOS/Windows in development | | **Winsandbox** | ❌ Not supported | ❌ Not supported | ✅ Windows only | Windows Sandbox environments only | -### VM Emulation Details - -#### Lume VM Support -- **macOS VMs**: ✅ Full native support with official images -- **Ubuntu/Linux VMs**: ⚠️ Limited support - requires custom image creation -- **Windows VMs**: ⚠️ Limited support - requires custom image creation - -#### Cloud VM Support -- **Ubuntu/Linux VMs**: ✅ Full support with managed cloud instances -- **macOS VMs**: 🚧 Coming soon - in development -- **Windows VMs**: 🚧 Coming soon - in development - -#### Windows Sandbox VM Support -- **Windows VMs**: ✅ Full support for Windows Sandbox environments -- **macOS/Linux VMs**: ❌ Not supported - Windows Sandbox only runs Windows - --- -## Installation Method Details +## Model Provider Compatibility -### playground-docker.sh -- **Containerized setup** using Docker -- Handles all dependencies automatically -- Requires Docker Desktop (Windows/macOS) or Docker Engine (Linux) -- **Windows note**: Must run in WSL2 environment +*This section shows which **AI model providers** are supported on each host operating system.* -### Dev Container -- **Development-focused** setup for contributors -- Integrates with VS Code and WindSurf IDEs -- Provides consistent development environment -- **Windows note**: Requires WSL2 backend for Docker - -### PyPI packages -- **Manual installation** via pip -- Most flexible installation method -- Allows custom configurations and integrations -- **Windows note**: Shell scripts require WSL2, but Python packages work natively - ---- - -## Legend - -- ✅ **Full support**: All features work natively without limitations -- ⚠️ **Partial support**: Requires additional setup (e.g., WSL2) or has limitations -- ❌ **Not supported**: Feature/provider combination is not available -- 🚧 **Coming soon**: Feature/provider combination is in development +| Provider | macOS Host | Ubuntu/Linux Host | Windows Host | Notes | +|----------|------------|-------------------|--------------|-------| +| **Anthropic** | ✅ Full support | ✅ Full support | ✅ Full support | Cloud-based API | +| **OpenAI** | ✅ Full support | ✅ Full support | ✅ Full support | Cloud-based API | +| **Ollama** | ✅ Full support | ✅ Full support | ✅ Full support | Local model serving | +| **OpenAI Compatible** | ✅ Full support | ✅ Full support | ✅ Full support | Any OpenAI-compatible API endpoint | +| **MLX VLM** | ✅ macOS only | ❌ Not supported | ❌ Not supported | Apple Silicon required. PyPI installation only. | \ No newline at end of file diff --git a/README.md b/README.md index fa4c2282..8caac383 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,19 @@ This repository includes a [Dev Container](./.devcontainer/README.md) configurat --- +### Option 3: PyPI +*Direct Python package installation* + +```bash +# conda create -yn cua python==3.12 + +pip install -U "cua-computer[all]" "cua-agent[all]" +python -m agent.ui # Start the agent UI +``` + +Or check out the [Usage Guide](#-usage-guide) to learn how to use our Python SDK in your own code. + +--- ## Supported [Agent Loops](https://github.com/trycua/cua/blob/main/libs/agent/README.md#agent-loops) - [UITARS-1.5](https://github.com/trycua/cua/blob/main/libs/agent/README.md#agent-loops) - Run locally on Apple Silicon with MLX, or use cloud providers @@ -88,16 +101,16 @@ This repository includes a [Dev Container](./.devcontainer/README.md) configurat - [Anthropic CUA](https://github.com/trycua/cua/blob/main/libs/agent/README.md#agent-loops) - Use Anthropic's Computer-Use capabilities - [OmniParser-v2.0](https://github.com/trycua/cua/blob/main/libs/agent/README.md#agent-loops) - Control UI with [Set-of-Marks prompting](https://som-gpt4v.github.io/) using any vision model -## 🖥️ OS Compatibility +## 🖥️ Compatibility -For detailed compatibility information including host OS support and VM emulation capabilities, see the [**Compatibility Matrix**](./COMPATIBILITY.md). +For detailed compatibility information including host OS support, VM emulation capabilities, and model provider compatibility, see the [Compatibility Matrix](./COMPATIBILITY.md).

-# 💻 Developer Guide +# 🐍 Usage Guide -Follow these steps to use C/ua in your own code. See [Developer Guide](./docs/Developer-Guide.md) for building from source. +Follow these steps to use C/ua in your own Python code. See [Developer Guide](./docs/Developer-Guide.md) for building from source. ### Step 1: Install Lume CLI From 87c1dd04516e00d1996d96cbacc3c07e5ec356b2 Mon Sep 17 00:00:00 2001 From: ddupont <3820588+ddupont808@users.noreply.github.com> Date: Thu, 19 Jun 2025 19:21:47 -0400 Subject: [PATCH 077/141] Added more interleaved images --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8caac383..c07df910 100644 --- a/README.md +++ b/README.md @@ -72,9 +72,11 @@ This repository includes a [Dev Container](./.devcontainer/README.md) configurat 1. **Install the Dev Containers extension ([VS Code](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) or [WindSurf](https://docs.windsurf.com/windsurf/advanced#dev-containers-beta))** 2. **Open the repository in the Dev Container:** - - Press `Ctrl+Shift+P` (or `Cmd+Shift+P` on macOS) - - Select `Dev Containers: Clone Repository in Container Volume...` and paste the repository URL: `https://github.com/trycua/cua.git` (if not cloned) or `Dev Containers: Open Folder in Container...` (if already cloned). **Note**: On WindSurf, the post install hook might not run automatically. If so, run `/bin/bash .devcontainer/post-install.sh` manually. -3. **Open the VS Code workspace:** Once the post-install.sh is done running, open the `.vscode/py.code-workspace` workspace and press "Open Workspace". + - Press `Ctrl+Shift+P` (or `⌘+Shift+P` on macOS) + - Select `Dev Containers: Clone Repository in Container Volume...` and paste the repository URL: `https://github.com/trycua/cua.git` (if not cloned) or `Dev Containers: Open Folder in Container...` (if git cloned). + > **Note**: On WindSurf, the post install hook might not run automatically. If so, run `/bin/bash .devcontainer/post-install.sh` manually. +3. **Open the VS Code workspace:** Once the post-install.sh is done running, open the `.vscode/py.code-workspace` workspace and press ![Open Workspace](https://github.com/user-attachments/assets/923bdd43-8c8f-4060-8d78-75bfa302b48c) +. 4. **Run the Agent UI example:** Click ![Run Agent UI](https://github.com/user-attachments/assets/7a61ef34-4b22-4dab-9864-f86bf83e290b) to start the Gradio UI. If prompted to install **debugpy (Python Debugger)** to enable remote debugging, select 'Yes' to proceed. 5. **Access the Gradio UI:** The Gradio UI will be available at `http://localhost:7860` and will automatically forward to your host machine. From 531fb7ccaf370cd615c84f66b3d0af9eb71f3829 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Fri, 20 Jun 2025 10:20:44 -0400 Subject: [PATCH 078/141] Normalize logging hierarchy (#294) --- libs/agent/agent/__init__.py | 2 +- libs/agent/agent/core/agent.py | 2 -- libs/agent/agent/core/telemetry.py | 2 +- libs/agent/agent/providers/omni/loop.py | 2 -- libs/agent/agent/providers/uitars/loop.py | 2 -- .../computer_server/diorama/diorama.py | 8 +------- libs/computer-server/computer_server/diorama/draw.py | 8 +------- libs/computer-server/computer_server/main.py | 2 +- libs/computer/computer/__init__.py | 4 ++-- libs/computer/computer/computer.py | 8 ++++---- libs/computer/computer/interface/linux.py | 2 +- libs/computer/computer/interface/macos.py | 2 +- libs/computer/computer/interface/windows.py | 2 +- libs/computer/computer/telemetry.py | 10 +++++----- libs/core/core/telemetry/client.py | 2 +- libs/core/core/telemetry/posthog_client.py | 2 +- libs/core/core/telemetry/sender.py | 2 +- libs/core/core/telemetry/telemetry.py | 11 +++++------ 18 files changed, 27 insertions(+), 46 deletions(-) diff --git a/libs/agent/agent/__init__.py b/libs/agent/agent/__init__.py index 230eb91a..70c20add 100644 --- a/libs/agent/agent/__init__.py +++ b/libs/agent/agent/__init__.py @@ -6,7 +6,7 @@ import logging __version__ = "0.1.0" # Initialize logging -logger = logging.getLogger("cua.agent") +logger = logging.getLogger("agent") # Initialize telemetry when the package is imported try: diff --git a/libs/agent/agent/core/agent.py b/libs/agent/agent/core/agent.py index 6f2c6278..e9d3b866 100644 --- a/libs/agent/agent/core/agent.py +++ b/libs/agent/agent/core/agent.py @@ -11,10 +11,8 @@ from .types import AgentResponse from .factory import LoopFactory from .provider_config import DEFAULT_MODELS, ENV_VARS -logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) - class ComputerAgent: """A computer agent that can perform automated tasks using natural language instructions.""" diff --git a/libs/agent/agent/core/telemetry.py b/libs/agent/agent/core/telemetry.py index 3c708b17..d3e33a25 100644 --- a/libs/agent/agent/core/telemetry.py +++ b/libs/agent/agent/core/telemetry.py @@ -34,7 +34,7 @@ flush = _default_flush is_telemetry_enabled = _default_is_telemetry_enabled is_telemetry_globally_disabled = _default_is_telemetry_globally_disabled -logger = logging.getLogger("cua.agent.telemetry") +logger = logging.getLogger("agent.telemetry") try: # Import from core telemetry diff --git a/libs/agent/agent/providers/omni/loop.py b/libs/agent/agent/providers/omni/loop.py index 751d4fd3..faffefc0 100644 --- a/libs/agent/agent/providers/omni/loop.py +++ b/libs/agent/agent/providers/omni/loop.py @@ -26,10 +26,8 @@ from .api_handler import OmniAPIHandler from .tools.manager import ToolManager from .tools import ToolResult -logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) - def extract_data(input_string: str, data_type: str) -> str: """Extract content from code blocks.""" pattern = f"```{data_type}" + r"(.*?)(```|$)" diff --git a/libs/agent/agent/providers/uitars/loop.py b/libs/agent/agent/providers/uitars/loop.py index 133a3b83..a28cfec4 100644 --- a/libs/agent/agent/providers/uitars/loop.py +++ b/libs/agent/agent/providers/uitars/loop.py @@ -25,10 +25,8 @@ from .prompts import COMPUTER_USE, SYSTEM_PROMPT, MAC_SPECIFIC_NOTES from .clients.oaicompat import OAICompatClient from .clients.mlxvlm import MLXVLMUITarsClient -logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) - class UITARSLoop(BaseLoop): """UI-TARS-specific implementation of the agent loop. diff --git a/libs/computer-server/computer_server/diorama/diorama.py b/libs/computer-server/computer_server/diorama/diorama.py index fc426a7c..09aa6434 100644 --- a/libs/computer-server/computer_server/diorama/diorama.py +++ b/libs/computer-server/computer_server/diorama/diorama.py @@ -15,13 +15,7 @@ from computer_server.diorama.diorama_computer import DioramaComputer from computer_server.handlers.macos import * # simple, nicely formatted logging -logging.basicConfig( - level=logging.INFO, - format='[%(asctime)s] [%(levelname)s] %(message)s', - datefmt='%H:%M:%S', - stream=sys.stdout -) -logger = logging.getLogger("diorama.virtual_desktop") +logger = logging.getLogger(__name__) automation_handler = MacOSAutomationHandler() diff --git a/libs/computer-server/computer_server/diorama/draw.py b/libs/computer-server/computer_server/diorama/draw.py index 9fce809f..e915b790 100644 --- a/libs/computer-server/computer_server/diorama/draw.py +++ b/libs/computer-server/computer_server/diorama/draw.py @@ -28,13 +28,7 @@ import functools import logging # simple, nicely formatted logging -logging.basicConfig( - level=logging.INFO, - format='[%(asctime)s] [%(levelname)s] %(message)s', - datefmt='%H:%M:%S', - stream=sys.stdout -) -logger = logging.getLogger("diorama.draw") +logger = logging.getLogger(__name__) from computer_server.diorama.safezone import ( get_menubar_bounds, diff --git a/libs/computer-server/computer_server/main.py b/libs/computer-server/computer_server/main.py index bdca3693..7a0dd515 100644 --- a/libs/computer-server/computer_server/main.py +++ b/libs/computer-server/computer_server/main.py @@ -12,8 +12,8 @@ import os import aiohttp # Set up logging with more detail -logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) # Configure WebSocket with larger message size WEBSOCKET_MAX_SIZE = 1024 * 1024 * 10 # 10MB limit diff --git a/libs/computer/computer/__init__.py b/libs/computer/computer/__init__.py index 90d20454..e2f66bfb 100644 --- a/libs/computer/computer/__init__.py +++ b/libs/computer/computer/__init__.py @@ -6,14 +6,14 @@ import sys __version__ = "0.1.0" # Initialize logging -logger = logging.getLogger("cua.computer") +logger = logging.getLogger("computer") # Initialize telemetry when the package is imported try: # Import from core telemetry from core.telemetry import ( - is_telemetry_enabled, flush, + is_telemetry_enabled, record_event, ) diff --git a/libs/computer/computer/computer.py b/libs/computer/computer/computer.py index 8596bc8b..7ba29ee6 100644 --- a/libs/computer/computer/computer.py +++ b/libs/computer/computer/computer.py @@ -85,7 +85,7 @@ class Computer: experiments: Optional list of experimental features to enable (e.g. ["app-use"]) """ - self.logger = Logger("cua.computer", verbosity) + self.logger = Logger("computer", verbosity) self.logger.info("Initializing Computer...") # Store original parameters @@ -132,11 +132,11 @@ class Computer: # Configure root logger self.verbosity = verbosity - self.logger = Logger("cua", verbosity) + self.logger = Logger("computer", verbosity) # Configure component loggers with proper hierarchy - self.vm_logger = Logger("cua.vm", verbosity) - self.interface_logger = Logger("cua.interface", verbosity) + self.vm_logger = Logger("computer.vm", verbosity) + self.interface_logger = Logger("computer.interface", verbosity) if not use_host_computer_server: if ":" not in image or len(image.split(":")) != 2: diff --git a/libs/computer/computer/interface/linux.py b/libs/computer/computer/interface/linux.py index e96cde50..23b542b0 100644 --- a/libs/computer/computer/interface/linux.py +++ b/libs/computer/computer/interface/linux.py @@ -30,7 +30,7 @@ class LinuxComputerInterface(BaseComputerInterface): self._command_lock = asyncio.Lock() # Lock to ensure only one command at a time # Set logger name for Linux interface - self.logger = Logger("cua.interface.linux", LogLevel.NORMAL) + self.logger = Logger("computer.interface.linux", LogLevel.NORMAL) @property def ws_uri(self) -> str: diff --git a/libs/computer/computer/interface/macos.py b/libs/computer/computer/interface/macos.py index 539303e4..a8821d60 100644 --- a/libs/computer/computer/interface/macos.py +++ b/libs/computer/computer/interface/macos.py @@ -29,7 +29,7 @@ class MacOSComputerInterface(BaseComputerInterface): self._command_lock = asyncio.Lock() # Lock to ensure only one command at a time # Set logger name for macOS interface - self.logger = Logger("cua.interface.macos", LogLevel.NORMAL) + self.logger = Logger("computer.interface.macos", LogLevel.NORMAL) @property def ws_uri(self) -> str: diff --git a/libs/computer/computer/interface/windows.py b/libs/computer/computer/interface/windows.py index 6acce1c1..b88c9138 100644 --- a/libs/computer/computer/interface/windows.py +++ b/libs/computer/computer/interface/windows.py @@ -30,7 +30,7 @@ class WindowsComputerInterface(BaseComputerInterface): self._command_lock = asyncio.Lock() # Lock to ensure only one command at a time # Set logger name for Windows interface - self.logger = Logger("cua.interface.windows", LogLevel.NORMAL) + self.logger = Logger("computer.interface.windows", LogLevel.NORMAL) @property def ws_uri(self) -> str: diff --git a/libs/computer/computer/telemetry.py b/libs/computer/computer/telemetry.py index 38be92a9..69d064f8 100644 --- a/libs/computer/computer/telemetry.py +++ b/libs/computer/computer/telemetry.py @@ -9,10 +9,10 @@ TELEMETRY_AVAILABLE = False try: from core.telemetry import ( - record_event, increment, is_telemetry_enabled, is_telemetry_globally_disabled, + record_event, ) def increment_counter(counter_name: str, value: int = 1) -> None: @@ -22,14 +22,14 @@ try: def set_dimension(name: str, value: Any) -> None: """Set a dimension that will be attached to all events.""" - logger = logging.getLogger("cua.computer.telemetry") + logger = logging.getLogger("computer.telemetry") logger.debug(f"Setting dimension {name}={value}") TELEMETRY_AVAILABLE = True - logger = logging.getLogger("cua.computer.telemetry") + logger = logging.getLogger("computer.telemetry") logger.info("Successfully imported telemetry") except ImportError as e: - logger = logging.getLogger("cua.computer.telemetry") + logger = logging.getLogger("computer.telemetry") logger.warning(f"Could not import telemetry: {e}") TELEMETRY_AVAILABLE = False @@ -40,7 +40,7 @@ def _noop(*args: Any, **kwargs: Any) -> None: pass -logger = logging.getLogger("cua.computer.telemetry") +logger = logging.getLogger("computer.telemetry") # If telemetry isn't available, use no-op functions if not TELEMETRY_AVAILABLE: diff --git a/libs/core/core/telemetry/client.py b/libs/core/core/telemetry/client.py index 22686890..d4eb9c70 100644 --- a/libs/core/core/telemetry/client.py +++ b/libs/core/core/telemetry/client.py @@ -15,7 +15,7 @@ from typing import Any, Dict, List, Optional from core import __version__ from core.telemetry.sender import send_telemetry -logger = logging.getLogger("cua.telemetry") +logger = logging.getLogger("core.telemetry") # Controls how frequently telemetry will be sent (percentage) TELEMETRY_SAMPLE_RATE = 5 # 5% sampling rate diff --git a/libs/core/core/telemetry/posthog_client.py b/libs/core/core/telemetry/posthog_client.py index 8ddb1dd2..ad8a7685 100644 --- a/libs/core/core/telemetry/posthog_client.py +++ b/libs/core/core/telemetry/posthog_client.py @@ -16,7 +16,7 @@ from typing import Any, Dict, List, Optional import posthog from core import __version__ -logger = logging.getLogger("cua.telemetry") +logger = logging.getLogger("core.telemetry") # Controls how frequently telemetry will be sent (percentage) TELEMETRY_SAMPLE_RATE = 100 # 100% sampling rate (was 5%) diff --git a/libs/core/core/telemetry/sender.py b/libs/core/core/telemetry/sender.py index db96b1ac..8772868f 100644 --- a/libs/core/core/telemetry/sender.py +++ b/libs/core/core/telemetry/sender.py @@ -3,7 +3,7 @@ import logging from typing import Any, Dict -logger = logging.getLogger("cua.telemetry") +logger = logging.getLogger("core.telemetry") def send_telemetry(payload: Dict[str, Any]) -> bool: diff --git a/libs/core/core/telemetry/telemetry.py b/libs/core/core/telemetry/telemetry.py index 2a6e052e..f01421cd 100644 --- a/libs/core/core/telemetry/telemetry.py +++ b/libs/core/core/telemetry/telemetry.py @@ -30,7 +30,7 @@ def _configure_telemetry_logging() -> None: level = logging.ERROR # Configure the main telemetry logger - telemetry_logger = logging.getLogger("cua.telemetry") + telemetry_logger = logging.getLogger("core.telemetry") telemetry_logger.setLevel(level) @@ -46,11 +46,11 @@ try: POSTHOG_AVAILABLE = True except ImportError: - logger = logging.getLogger("cua.telemetry") + logger = logging.getLogger("core.telemetry") logger.info("PostHog not available. Install with: pdm add posthog") POSTHOG_AVAILABLE = False -logger = logging.getLogger("cua.telemetry") +logger = logging.getLogger("core.telemetry") # Check environment variables for global telemetry opt-out @@ -292,10 +292,9 @@ def set_telemetry_log_level(level: Optional[int] = None) -> None: # Set the level for all telemetry-related loggers telemetry_loggers = [ - "cua.telemetry", "core.telemetry", - "cua.agent.telemetry", - "cua.computer.telemetry", + "agent.telemetry", + "computer.telemetry", "posthog", ] From 3b42863806b92fa45c1bdcc24c1b11eb549680d9 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Fri, 20 Jun 2025 10:42:13 -0400 Subject: [PATCH 079/141] Bump version to fix local build --- libs/agent/pyproject.toml | 2 +- libs/computer/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/agent/pyproject.toml b/libs/agent/pyproject.toml index 8ea6a3fc..1ea963bd 100644 --- a/libs/agent/pyproject.toml +++ b/libs/agent/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "pdm.backend" [project] name = "cua-agent" -version = "0.1.0" +version = "0.2.0" description = "CUA (Computer Use) Agent for AI-driven computer interaction" readme = "README.md" authors = [ diff --git a/libs/computer/pyproject.toml b/libs/computer/pyproject.toml index c9aa46da..04bd2dfb 100644 --- a/libs/computer/pyproject.toml +++ b/libs/computer/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "pdm.backend" [project] name = "cua-computer" -version = "0.1.0" +version = "0.2.0" description = "Computer-Use Interface (CUI) framework powering Cua" readme = "README.md" authors = [ From 6217c9e83059081d60a6efe485588e2fe3440ca9 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Fri, 20 Jun 2025 10:50:43 -0400 Subject: [PATCH 080/141] Removed all print statements --- libs/computer/computer/helpers.py | 5 +- libs/computer/computer/providers/lume_api.py | 13 -- .../computer/providers/lumier/provider.py | 121 +++++++++--------- 3 files changed, 61 insertions(+), 78 deletions(-) diff --git a/libs/computer/computer/helpers.py b/libs/computer/computer/helpers.py index b472c047..8317b8d9 100644 --- a/libs/computer/computer/helpers.py +++ b/libs/computer/computer/helpers.py @@ -1,6 +1,7 @@ """ Helper functions and decorators for the Computer module. """ +import logging import asyncio from functools import wraps from typing import Any, Callable, Optional, TypeVar, cast @@ -8,6 +9,8 @@ from typing import Any, Callable, Optional, TypeVar, cast # Global reference to the default computer instance _default_computer = None +logger = logging.getLogger(__name__) + def set_default_computer(computer): """ Set the default computer instance to be used by the remote decorator. @@ -41,7 +44,7 @@ def sandboxed(venv_name: str = "default", computer: str = "default", max_retries try: return await comp.venv_exec(venv_name, func, *args, **kwargs) except Exception as e: - print(f"Attempt {i+1} failed: {e}") + logger.error(f"Attempt {i+1} failed: {e}") await asyncio.sleep(1) if i == max_retries - 1: raise e diff --git a/libs/computer/computer/providers/lume_api.py b/libs/computer/computer/providers/lume_api.py index fbfaca4b..3cbe1097 100644 --- a/libs/computer/computer/providers/lume_api.py +++ b/libs/computer/computer/providers/lume_api.py @@ -66,8 +66,6 @@ def lume_api_get( # Only print the curl command when debug is enabled display_curl_string = ' '.join(display_cmd) - if debug or verbose: - print(f"DEBUG: Executing curl API call: {display_curl_string}") logger.debug(f"Executing API request: {display_curl_string}") # Execute the command - for execution we need to use shell=True to handle URLs with special characters @@ -172,8 +170,6 @@ def lume_api_run( payload["sharedDirectories"] = run_opts["shared_directories"] # Log the payload for debugging - if debug or verbose: - print(f"DEBUG: Payload for {vm_name} run request: {json.dumps(payload, indent=2)}") logger.debug(f"API payload: {json.dumps(payload, indent=2)}") # Construct the curl command @@ -184,11 +180,6 @@ def lume_api_run( api_url ] - # Always print the command for debugging - if debug or verbose: - print(f"DEBUG: Executing curl run API call: {' '.join(cmd)}") - print(f"Run payload: {json.dumps(payload, indent=2)}") - # Execute the command try: result = subprocess.run(cmd, capture_output=True, text=True) @@ -405,8 +396,6 @@ def lume_api_pull( f"http://{host}:{port}/lume/pull" ]) - if debug or verbose: - print(f"DEBUG: Executing curl API call: {' '.join(pull_cmd)}") logger.debug(f"Executing API request: {' '.join(pull_cmd)}") try: @@ -474,8 +463,6 @@ def lume_api_delete( # Only print the curl command when debug is enabled display_curl_string = ' '.join(display_cmd) - if debug or verbose: - print(f"DEBUG: Executing curl API call: {display_curl_string}") logger.debug(f"Executing API request: {display_curl_string}") # Execute the command - for execution we need to use shell=True to handle URLs with special characters diff --git a/libs/computer/computer/providers/lumier/provider.py b/libs/computer/computer/providers/lumier/provider.py index 14c5620d..67f348be 100644 --- a/libs/computer/computer/providers/lumier/provider.py +++ b/libs/computer/computer/providers/lumier/provider.py @@ -305,7 +305,7 @@ class LumierProvider(BaseVMProvider): cmd = ["docker", "run", "-d", "--name", self.container_name] cmd.extend(["-p", f"{self.vnc_port}:8006"]) - print(f"Using specified noVNC_port: {self.vnc_port}") + logger.debug(f"Using specified noVNC_port: {self.vnc_port}") # Set API URL using the API port self._api_url = f"http://{self.host}:{self.api_port}" @@ -324,7 +324,7 @@ class LumierProvider(BaseVMProvider): "-v", f"{storage_dir}:/storage", "-e", f"HOST_STORAGE_PATH={storage_dir}" ]) - print(f"Using persistent storage at: {storage_dir}") + logger.debug(f"Using persistent storage at: {storage_dir}") # Add shared folder volume mount if shared_path is specified if self.shared_path: @@ -337,12 +337,12 @@ class LumierProvider(BaseVMProvider): "-v", f"{shared_dir}:/shared", "-e", f"HOST_SHARED_PATH={shared_dir}" ]) - print(f"Using shared folder at: {shared_dir}") + logger.debug(f"Using shared folder at: {shared_dir}") # Add environment variables # Always use the container_name as the VM_NAME for consistency # Use the VM image passed from the Computer class - print(f"Using VM image: {self.image}") + logger.debug(f"Using VM image: {self.image}") # If ghcr.io is in the image, use the full image name if "ghcr.io" in self.image: @@ -362,22 +362,22 @@ class LumierProvider(BaseVMProvider): # First check if the image exists locally try: - print(f"Checking if Docker image {lumier_image} exists locally...") + logger.debug(f"Checking if Docker image {lumier_image} exists locally...") check_image_cmd = ["docker", "image", "inspect", lumier_image] subprocess.run(check_image_cmd, capture_output=True, check=True) - print(f"Docker image {lumier_image} found locally.") + logger.debug(f"Docker image {lumier_image} found locally.") except subprocess.CalledProcessError: # Image doesn't exist locally - print(f"\nWARNING: Docker image {lumier_image} not found locally.") - print("The system will attempt to pull it from Docker Hub, which may fail if you have network connectivity issues.") - print("If the Docker pull fails, you may need to manually pull the image first with:") - print(f" docker pull {lumier_image}\n") + logger.warning(f"\nWARNING: Docker image {lumier_image} not found locally.") + logger.warning("The system will attempt to pull it from Docker Hub, which may fail if you have network connectivity issues.") + logger.warning("If the Docker pull fails, you may need to manually pull the image first with:") + logger.warning(f" docker pull {lumier_image}\n") # Add the image to the command cmd.append(lumier_image) # Print the Docker command for debugging - print(f"DOCKER COMMAND: {' '.join(cmd)}") + logger.debug(f"DOCKER COMMAND: {' '.join(cmd)}") # Run the container with improved error handling try: @@ -395,8 +395,8 @@ class LumierProvider(BaseVMProvider): raise # Container started, now check VM status with polling - print("Container started, checking VM status...") - print("NOTE: This may take some time while the VM image is being pulled and initialized") + logger.debug("Container started, checking VM status...") + logger.debug("NOTE: This may take some time while the VM image is being pulled and initialized") # Start a background thread to show container logs in real-time import threading @@ -404,8 +404,8 @@ class LumierProvider(BaseVMProvider): def show_container_logs(): # Give the container a moment to start generating logs time.sleep(1) - print(f"\n---- CONTAINER LOGS FOR '{name}' (LIVE) ----") - print("Showing logs as they are generated. Press Ctrl+C to stop viewing logs...\n") + logger.debug(f"\n---- CONTAINER LOGS FOR '{name}' (LIVE) ----") + logger.debug("Showing logs as they are generated. Press Ctrl+C to stop viewing logs...\n") try: # Use docker logs with follow option @@ -415,17 +415,17 @@ class LumierProvider(BaseVMProvider): # Read and print logs line by line for line in process.stdout: - print(line, end='') + logger.debug(line, end='') # Break if process has exited if process.poll() is not None: break except Exception as e: - print(f"\nError showing container logs: {e}") + logger.error(f"\nError showing container logs: {e}") if self.verbose: logger.error(f"Error in log streaming thread: {e}") finally: - print("\n---- LOG STREAMING ENDED ----") + logger.debug("\n---- LOG STREAMING ENDED ----") # Make sure process is terminated if 'process' in locals() and process.poll() is None: process.terminate() @@ -452,11 +452,11 @@ class LumierProvider(BaseVMProvider): else: wait_time = min(30, 5 + (attempt * 2)) - print(f"Waiting {wait_time}s before retry #{attempt+1}...") + logger.debug(f"Waiting {wait_time}s before retry #{attempt+1}...") await asyncio.sleep(wait_time) # Try to get VM status - print(f"Checking VM status (attempt {attempt+1})...") + logger.debug(f"Checking VM status (attempt {attempt+1})...") vm_status = await self.get_vm(name) # Check for API errors @@ -468,20 +468,20 @@ class LumierProvider(BaseVMProvider): # since _lume_api_get already logged the technical details if consecutive_errors == 1 or attempt % 5 == 0: if 'Empty reply from server' in error_msg: - print("API server is starting up - container is running, but API isn't fully initialized yet.") - print("This is expected during the initial VM setup - will continue polling...") + logger.info("API server is starting up - container is running, but API isn't fully initialized yet.") + logger.info("This is expected during the initial VM setup - will continue polling...") else: # Don't repeat the exact same error message each time - logger.debug(f"API request error (attempt {attempt+1}): {error_msg}") + logger.warning(f"API request error (attempt {attempt+1}): {error_msg}") # Just log that we're still working on it if attempt > 3: - print("Still waiting for the API server to become available...") + logger.debug("Still waiting for the API server to become available...") # If we're getting errors but container is running, that's normal during startup if vm_status.get('status') == 'running': if not vm_running: - print("Container is running, waiting for the VM within it to become fully ready...") - print("This might take a minute while the VM initializes...") + logger.info("Container is running, waiting for the VM within it to become fully ready...") + logger.info("This might take a minute while the VM initializes...") vm_running = True # Increase counter and continue @@ -497,35 +497,35 @@ class LumierProvider(BaseVMProvider): # Check if we have an IP address, which means the VM is fully ready if 'ip_address' in vm_status and vm_status['ip_address']: - print(f"VM is now fully running with IP: {vm_status.get('ip_address')}") + logger.info(f"VM is now fully running with IP: {vm_status.get('ip_address')}") if 'vnc_url' in vm_status and vm_status['vnc_url']: - print(f"VNC URL: {vm_status.get('vnc_url')}") + logger.info(f"VNC URL: {vm_status.get('vnc_url')}") return vm_status else: - print("VM is running but still initializing network interfaces...") - print("Waiting for IP address to be assigned...") + logger.debug("VM is running but still initializing network interfaces...") + logger.debug("Waiting for IP address to be assigned...") else: # VM exists but might still be starting up status = vm_status.get('status', 'unknown') - print(f"VM found but status is: {status}. Continuing to poll...") + logger.debug(f"VM found but status is: {status}. Continuing to poll...") # Increase counter for next iteration's delay calculation attempt += 1 # If we reach a very large number of attempts, give a reassuring message but continue if attempt % 10 == 0: - print(f"Still waiting after {attempt} attempts. This might take several minutes for first-time setup.") + logger.debug(f"Still waiting after {attempt} attempts. This might take several minutes for first-time setup.") if not vm_running and attempt >= 20: - print("\nNOTE: First-time VM initialization can be slow as images are downloaded.") - print("If this continues for more than 10 minutes, you may want to check:") - print(" 1. Docker logs with: docker logs " + name) - print(" 2. If your network can access container registries") - print("Press Ctrl+C to abort if needed.\n") + logger.warning("\nNOTE: First-time VM initialization can be slow as images are downloaded.") + logger.warning("If this continues for more than 10 minutes, you may want to check:") + logger.warning(" 1. Docker logs with: docker logs " + name) + logger.warning(" 2. If your network can access container registries") + logger.warning("Press Ctrl+C to abort if needed.\n") # After 150 attempts (likely over 30-40 minutes), return current status if attempt >= 150: - print(f"Reached 150 polling attempts. VM status is: {vm_status.get('status', 'unknown')}") - print("Returning current VM status, but please check Docker logs if there are issues.") + logger.debug(f"Reached 150 polling attempts. VM status is: {vm_status.get('status', 'unknown')}") + logger.debug("Returning current VM status, but please check Docker logs if there are issues.") return vm_status except Exception as e: @@ -535,9 +535,9 @@ class LumierProvider(BaseVMProvider): # If we've had too many consecutive errors, might be a deeper problem if consecutive_errors >= 10: - print(f"\nWARNING: Encountered {consecutive_errors} consecutive errors while checking VM status.") - print("You may need to check the Docker container logs or restart the process.") - print(f"Error details: {str(e)}\n") + logger.warning(f"\nWARNING: Encountered {consecutive_errors} consecutive errors while checking VM status.") + logger.warning("You may need to check the Docker container logs or restart the process.") + logger.warning(f"Error details: {str(e)}\n") # Increase attempt counter for next iteration attempt += 1 @@ -545,7 +545,7 @@ class LumierProvider(BaseVMProvider): # After many consecutive errors, add a delay to avoid hammering the system if attempt > 5: error_delay = min(30, 10 + attempt) - print(f"Multiple connection errors, waiting {error_delay}s before next attempt...") + logger.warning(f"Multiple connection errors, waiting {error_delay}s before next attempt...") await asyncio.sleep(error_delay) except subprocess.CalledProcessError as e: @@ -568,7 +568,7 @@ class LumierProvider(BaseVMProvider): api_ready = False container_running = False - print(f"Waiting for container {container_name} to be ready (timeout: {timeout}s)...") + logger.debug(f"Waiting for container {container_name} to be ready (timeout: {timeout}s)...") while time.time() - start_time < timeout: # Check if container is running @@ -579,7 +579,6 @@ class LumierProvider(BaseVMProvider): if container_status and container_status.startswith("Up"): container_running = True - print(f"Container {container_name} is running") logger.info(f"Container {container_name} is running with status: {container_status}") else: logger.warning(f"Container {container_name} not yet running, status: {container_status}") @@ -603,7 +602,6 @@ class LumierProvider(BaseVMProvider): if result.returncode == 0 and "ok" in result.stdout.lower(): api_ready = True - print(f"API is ready at {api_url}") logger.info(f"API is ready at {api_url}") break else: @@ -621,7 +619,6 @@ class LumierProvider(BaseVMProvider): if vm_result.returncode == 0 and vm_result.stdout.strip(): # VM API responded with something - consider the API ready api_ready = True - print(f"VM API is ready at {vm_api_url}") logger.info(f"VM API is ready at {vm_api_url}") break else: @@ -643,7 +640,6 @@ class LumierProvider(BaseVMProvider): else: curl_error = f"Unknown curl error code: {curl_code}" - print(f"API not ready yet: {curl_error}") logger.info(f"API not ready yet: {curl_error}") except subprocess.SubprocessError as e: logger.warning(f"Error checking API status: {e}") @@ -652,22 +648,19 @@ class LumierProvider(BaseVMProvider): # a bit longer before checking again, as the container may still be initializing elapsed_seconds = time.time() - start_time if int(elapsed_seconds) % 5 == 0: # Only print status every 5 seconds to reduce verbosity - print(f"Waiting for API to initialize... ({elapsed_seconds:.1f}s / {timeout}s)") + logger.debug(f"Waiting for API to initialize... ({elapsed_seconds:.1f}s / {timeout}s)") await asyncio.sleep(3) # Longer sleep between API checks # Handle timeout - if the container is running but API is not ready, that's not # necessarily an error - the API might just need more time to start up if not container_running: - print(f"Timed out waiting for container {container_name} to start") logger.warning(f"Timed out waiting for container {container_name} to start") return False if not api_ready: - print(f"Container {container_name} is running, but API is not fully ready yet.") - print("Proceeding with operations. API will become available shortly.") - print("NOTE: You may see some 'API request failed' messages while the API initializes.") logger.warning(f"Container {container_name} is running, but API is not fully ready yet.") + logger.warning(f"NOTE: You may see some 'API request failed' messages while the API initializes.") # Return True if container is running, even if API isn't ready yet # This allows VM operations to proceed, with appropriate retries for API calls @@ -777,8 +770,8 @@ class LumierProvider(BaseVMProvider): # For follow mode with timeout, we'll run the command and handle the timeout log_cmd.append(container_name) logger.info(f"Following logs for container '{container_name}' with timeout {timeout}s") - print(f"\n---- CONTAINER LOGS FOR '{container_name}' (LIVE) ----") - print(f"Press Ctrl+C to stop following logs\n") + logger.info(f"\n---- CONTAINER LOGS FOR '{container_name}' (LIVE) ----") + logger.info(f"Press Ctrl+C to stop following logs\n") try: # Run with timeout @@ -790,7 +783,7 @@ class LumierProvider(BaseVMProvider): process.wait(timeout=timeout) except subprocess.TimeoutExpired: process.terminate() # Stop after timeout - print(f"\n---- LOG FOLLOWING STOPPED (timeout {timeout}s reached) ----") + logger.info(f"\n---- LOG FOLLOWING STOPPED (timeout {timeout}s reached) ----") else: # Without timeout, wait for user interruption process.wait() @@ -798,14 +791,14 @@ class LumierProvider(BaseVMProvider): return "Logs were displayed to console in follow mode" except KeyboardInterrupt: process.terminate() - print("\n---- LOG FOLLOWING STOPPED (user interrupted) ----") + logger.info("\n---- LOG FOLLOWING STOPPED (user interrupted) ----") return "Logs were displayed to console in follow mode (interrupted)" else: # For follow mode without timeout, we'll print a helpful message log_cmd.append(container_name) logger.info(f"Following logs for container '{container_name}' indefinitely") - print(f"\n---- CONTAINER LOGS FOR '{container_name}' (LIVE) ----") - print(f"Press Ctrl+C to stop following logs\n") + logger.info(f"\n---- CONTAINER LOGS FOR '{container_name}' (LIVE) ----") + logger.info(f"Press Ctrl+C to stop following logs\n") try: # Run the command and let it run until interrupted @@ -814,7 +807,7 @@ class LumierProvider(BaseVMProvider): return "Logs were displayed to console in follow mode" except KeyboardInterrupt: process.terminate() - print("\n---- LOG FOLLOWING STOPPED (user interrupted) ----") + logger.info("\n---- LOG FOLLOWING STOPPED (user interrupted) ----") return "Logs were displayed to console in follow mode (interrupted)" else: # For non-follow mode, capture and return the logs as a string @@ -827,11 +820,11 @@ class LumierProvider(BaseVMProvider): # Only print header and logs if there's content if logs.strip(): - print(f"\n---- CONTAINER LOGS FOR '{container_name}' (LAST {num_lines} LINES) ----\n") - print(logs) - print(f"\n---- END OF LOGS ----") + logger.info(f"\n---- CONTAINER LOGS FOR '{container_name}' (LAST {num_lines} LINES) ----\n") + logger.info(logs) + logger.info(f"\n---- END OF LOGS ----") else: - print(f"\nNo logs available for container '{container_name}'") + logger.info(f"\nNo logs available for container '{container_name}'") return logs except subprocess.CalledProcessError as e: From c8e4236257039629eafa9f3c97f35ebc5ff323d1 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Fri, 20 Jun 2025 11:24:50 -0400 Subject: [PATCH 081/141] Remove patch, use official mlx-vlm --- libs/agent/README.md | 5 +---- libs/agent/pyproject.toml | 10 ++++------ scripts/playground.sh | 7 ------- 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/libs/agent/README.md b/libs/agent/README.md index 31d1accd..d1c82a5e 100644 --- a/libs/agent/README.md +++ b/libs/agent/README.md @@ -34,10 +34,7 @@ pip install "cua-agent[anthropic]" # Anthropic Cua Loop pip install "cua-agent[uitars]" # UI-Tars support pip install "cua-agent[omni]" # Cua Loop based on OmniParser (includes Ollama for local models) pip install "cua-agent[ui]" # Gradio UI for the agent - -# For local UI-TARS with MLX support, you need to manually install mlx-vlm: -pip install "cua-agent[uitars-mlx]" -pip install git+https://github.com/ddupont808/mlx-vlm.git@stable/fix/qwen2-position-id # PR: https://github.com/Blaizzy/mlx-vlm/pull/349 +pip install "cua-agent[uitars-mlx]" # MLX UI-Tars support ``` ## Run diff --git a/libs/agent/pyproject.toml b/libs/agent/pyproject.toml index 8ea6a3fc..cb861712 100644 --- a/libs/agent/pyproject.toml +++ b/libs/agent/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "pdm.backend" [project] name = "cua-agent" -version = "0.1.0" +version = "0.2.0" description = "CUA (Computer Use) Agent for AI-driven computer interaction" readme = "README.md" authors = [ @@ -38,8 +38,7 @@ uitars = [ "httpx>=0.27.0,<0.29.0", ] uitars-mlx = [ - # The mlx-vlm package needs to be installed manually with: - # pip install git+https://github.com/ddupont808/mlx-vlm.git@stable/fix/qwen2-position-id + "mlx-vlm>=0.1.27" ] ui = [ "gradio>=5.23.3,<6.0.0", @@ -88,9 +87,8 @@ all = [ "requests>=2.31.0,<3.0.0", "ollama>=0.4.7,<0.5.0", "gradio>=5.23.3,<6.0.0", - "python-dotenv>=1.0.1,<2.0.0" - # mlx-vlm needs to be installed manually with: - # pip install git+https://github.com/ddupont808/mlx-vlm.git@stable/fix/qwen2-position-id + "python-dotenv>=1.0.1,<2.0.0", + "mlx-vlm>=0.1.27" ] [tool.pdm] diff --git a/scripts/playground.sh b/scripts/playground.sh index 4cdb1ffa..9be712d2 100755 --- a/scripts/playground.sh +++ b/scripts/playground.sh @@ -209,13 +209,6 @@ echo "📦 Updating C/ua packages..." pip install -U pip setuptools wheel Cmake pip install -U cua-computer "cua-agent[all]" -# Install mlx-vlm on Apple Silicon Macs -if [[ $(uname -m) == 'arm64' ]]; then - echo "Installing mlx-vlm for Apple Silicon Macs..." - pip install git+https://github.com/Blaizzy/mlx-vlm.git - # pip install git+https://github.com/ddupont808/mlx-vlm.git@stable/fix/qwen2-position-id -fi - # Create a simple demo script mkdir -p "$DEMO_DIR" From e3d521454631049631522d23b6119192ec41448d Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Fri, 20 Jun 2025 11:29:00 -0400 Subject: [PATCH 082/141] Added platform specific markers --- libs/agent/pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/agent/pyproject.toml b/libs/agent/pyproject.toml index cb861712..f078a5c9 100644 --- a/libs/agent/pyproject.toml +++ b/libs/agent/pyproject.toml @@ -38,7 +38,7 @@ uitars = [ "httpx>=0.27.0,<0.29.0", ] uitars-mlx = [ - "mlx-vlm>=0.1.27" + "mlx-vlm>=0.1.27; sys_platform == 'darwin'" ] ui = [ "gradio>=5.23.3,<6.0.0", @@ -88,7 +88,7 @@ all = [ "ollama>=0.4.7,<0.5.0", "gradio>=5.23.3,<6.0.0", "python-dotenv>=1.0.1,<2.0.0", - "mlx-vlm>=0.1.27" + "mlx-vlm>=0.1.27; sys_platform == 'darwin'" ] [tool.pdm] From 00919f0365d993341655cc7e97707227fc7019a7 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Fri, 20 Jun 2025 10:16:37 -0700 Subject: [PATCH 083/141] Fix issues with websocket response handling, export types, update readme -- it works! --- libs/computer/typescript/README.md | 15 +- .../src/computer/providers/cloud.ts | 65 ++----- libs/computer/typescript/src/index.ts | 2 + .../computer/typescript/src/interface/base.ts | 19 ++- .../typescript/src/interface/macos.ts | 158 +++++++++++------- 5 files changed, 127 insertions(+), 132 deletions(-) diff --git a/libs/computer/typescript/README.md b/libs/computer/typescript/README.md index 6babcac6..77e2c47c 100644 --- a/libs/computer/typescript/README.md +++ b/libs/computer/typescript/README.md @@ -20,12 +20,10 @@ pnpm add @cua/computer import { Computer } from '@cua/computer'; // Create a new computer instance -const computer = new Computer({ - display: '1024x768', - memory: '8GB', - cpu: '4', - osType: 'macos', - image: 'macos-sequoia-cua:latest' +const computer = new CloudComputer({ + osType: OSType.LINUX, + name: 's-linux-vm_id' + apiKey: 'your-api-key' }); // Start the computer @@ -59,12 +57,12 @@ The library is organized into the following structure: ### Provider Implementations -- **LumeComputer**: Implementation for Lume API-based VMs +- **LumeComputer**: Implementation for Lume API-based VMs (Unmaintained) - **CloudComputer**: Implementation for cloud-based VMs ### Utility Functions -- **Lume API Utilities**: Functions for interacting with the Lume API (lumeApiGet, lumeApiRun, lumeApiStop, etc.) +- **Lume API Utilities**: Functions for interacting with the Lume API (lumeApiGet, lumeApiRun, lumeApiStop, etc.) (Unmaintained) - **Helper Functions**: Parsing utilities for display and memory strings ## Development @@ -97,7 +95,6 @@ pnpm typecheck **WARNING:** Some parts of this library, particularly the provider implementations (like Lume), were created as test/example implementations and are not maintained or expected to work in production environments. They serve as references for how providers might be implemented but should not be used in production. - ## License [MIT](./LICENSE) License 2025 [C/UA](https://github.com/trycua) diff --git a/libs/computer/typescript/src/computer/providers/cloud.ts b/libs/computer/typescript/src/computer/providers/cloud.ts index d8f9f1cc..7e8df495 100644 --- a/libs/computer/typescript/src/computer/providers/cloud.ts +++ b/libs/computer/typescript/src/computer/providers/cloud.ts @@ -12,9 +12,9 @@ const logger = pino({ name: "computer-cloud" }); * Cloud-specific computer implementation */ export class CloudComputer extends BaseComputer { - protected apiKey: string; protected static vmProviderType: VMProviderType.CLOUD; - private interface?: BaseComputerInterface; + protected apiKey: string; + private iface?: BaseComputerInterface; private initialized = false; constructor(config: CloudComputerConfig) { @@ -23,7 +23,8 @@ export class CloudComputer extends BaseComputer { } get ip() { - return `${this.name}.containers.cloud.trycua.com`; + return "192.168.64.9"; + //return `${this.name}.containers.cloud.trycua.com`; } /** @@ -43,7 +44,7 @@ export class CloudComputer extends BaseComputer { logger.info(`Connecting to cloud VM at ${ipAddress}`); // Create the interface with API key authentication - this.interface = InterfaceFactory.createInterfaceForOS( + this.iface = InterfaceFactory.createInterfaceForOS( this.osType, ipAddress, this.apiKey, @@ -52,7 +53,7 @@ export class CloudComputer extends BaseComputer { // Wait for the interface to be ready logger.info("Waiting for interface to be ready..."); - await this.interface.waitForReady(); + await this.iface.waitForReady(); this.initialized = true; logger.info("Cloud computer ready"); @@ -68,9 +69,9 @@ export class CloudComputer extends BaseComputer { async stop(): Promise { logger.info("Stopping cloud computer..."); - if (this.interface) { - this.interface.disconnect(); - this.interface = undefined; + if (this.iface) { + this.iface.disconnect(); + this.iface = undefined; } this.initialized = false; @@ -80,53 +81,11 @@ export class CloudComputer extends BaseComputer { /** * Get the computer interface */ - getInterface(): BaseComputerInterface { - if (!this.interface) { + get interface(): BaseComputerInterface { + if (!this.iface) { throw new Error("Computer not initialized. Call run() first."); } - return this.interface; - } - - /** - * Take a screenshot - */ - async screenshot(): Promise { - return this.getInterface().screenshot(); - } - - /** - * Click at coordinates - */ - async click(x?: number, y?: number): Promise { - return this.getInterface().leftClick(x, y); - } - - /** - * Type text - */ - async type(text: string): Promise { - return this.getInterface().typeText(text); - } - - /** - * Press a key - */ - async key(key: string): Promise { - return this.getInterface().pressKey(key); - } - - /** - * Press hotkey combination - */ - async hotkey(...keys: string[]): Promise { - return this.getInterface().hotkey(...keys); - } - - /** - * Run a command - */ - async runCommand(command: string): Promise<[string, string]> { - return this.getInterface().runCommand(command); + return this.iface; } /** diff --git a/libs/computer/typescript/src/index.ts b/libs/computer/typescript/src/index.ts index 35ea7c0d..ef312139 100644 --- a/libs/computer/typescript/src/index.ts +++ b/libs/computer/typescript/src/index.ts @@ -2,3 +2,5 @@ export * from "./computer"; //todo: figure out what types to export and how to do that +// +export { OSType } from "./types"; diff --git a/libs/computer/typescript/src/interface/base.ts b/libs/computer/typescript/src/interface/base.ts index 6455fb29..6f012fe6 100644 --- a/libs/computer/typescript/src/interface/base.ts +++ b/libs/computer/typescript/src/interface/base.ts @@ -75,12 +75,12 @@ export abstract class BaseComputerInterface { */ protected get wsUri(): string { const protocol = this.secure ? "wss" : "ws"; - + // Check if ipAddress already includes a port if (this.ipAddress.includes(":")) { return `${protocol}://${this.ipAddress}/ws`; } - + // Otherwise, append the default port const port = this.secure ? "8443" : "8000"; return `${protocol}://${this.ipAddress}:${port}/ws`; @@ -162,10 +162,10 @@ export abstract class BaseComputerInterface { /** * Send a command to the WebSocket server. */ - public async sendCommand(command: { - action: string; - [key: string]: unknown; - }): Promise<{ [key: string]: unknown }> { + public async sendCommand( + command: string, + params: { [key: string]: unknown } = {} + ): Promise<{ [key: string]: unknown }> { // Create a new promise for this specific command const commandPromise = new Promise<{ [key: string]: unknown }>( (resolve, reject) => { @@ -190,11 +190,12 @@ export abstract class BaseComputerInterface { } catch (error) { innerReject(error); } - this.ws!.off("message", messageHandler); + this.ws.off("message", messageHandler); }; - this.ws!.on("message", messageHandler); - this.ws!.send(JSON.stringify(command)); + this.ws.on("message", messageHandler); + const wsCommand = { command, params }; + this.ws.send(JSON.stringify(wsCommand)); } ); }; diff --git a/libs/computer/typescript/src/interface/macos.ts b/libs/computer/typescript/src/interface/macos.ts index 51df1294..d34ed984 100644 --- a/libs/computer/typescript/src/interface/macos.ts +++ b/libs/computer/typescript/src/interface/macos.ts @@ -13,7 +13,7 @@ export class MacOSComputerInterface extends BaseComputerInterface { y?: number, button: MouseButton = "left" ): Promise { - await this.sendCommand({ action: "mouse_down", x, y, button }); + await this.sendCommand("mouse_down", { x, y, button }); } async mouseUp( @@ -21,23 +21,23 @@ export class MacOSComputerInterface extends BaseComputerInterface { y?: number, button: MouseButton = "left" ): Promise { - await this.sendCommand({ action: "mouse_up", x, y, button }); + await this.sendCommand("mouse_up", { x, y, button }); } async leftClick(x?: number, y?: number): Promise { - await this.sendCommand({ action: "left_click", x, y }); + await this.sendCommand("left_click", { x, y }); } async rightClick(x?: number, y?: number): Promise { - await this.sendCommand({ action: "right_click", x, y }); + await this.sendCommand("right_click", { x, y }); } async doubleClick(x?: number, y?: number): Promise { - await this.sendCommand({ action: "double_click", x, y }); + await this.sendCommand("double_click", { x, y }); } async moveCursor(x: number, y: number): Promise { - await this.sendCommand({ action: "move_cursor", x, y }); + await this.sendCommand("move_cursor", { x, y }); } async dragTo( @@ -46,7 +46,7 @@ export class MacOSComputerInterface extends BaseComputerInterface { button: MouseButton = "left", duration = 0.5 ): Promise { - await this.sendCommand({ action: "drag_to", x, y, button, duration }); + await this.sendCommand("drag_to", { x, y, button, duration }); } async drag( @@ -54,154 +54,190 @@ export class MacOSComputerInterface extends BaseComputerInterface { button: MouseButton = "left", duration = 0.5 ): Promise { - await this.sendCommand({ action: "drag", path, button, duration }); + await this.sendCommand("drag", { path, button, duration }); } // Keyboard Actions async keyDown(key: string): Promise { - await this.sendCommand({ action: "key_down", key }); + await this.sendCommand("key_down", { key }); } async keyUp(key: string): Promise { - await this.sendCommand({ action: "key_up", key }); + await this.sendCommand("key_up", { key }); } async typeText(text: string): Promise { - await this.sendCommand({ action: "type_text", text }); + await this.sendCommand("type_text", { text }); } async pressKey(key: string): Promise { - await this.sendCommand({ action: "press_key", key }); + await this.sendCommand("press_key", { key }); } async hotkey(...keys: string[]): Promise { - await this.sendCommand({ action: "hotkey", keys }); + await this.sendCommand("hotkey", { keys }); } // Scrolling Actions async scroll(x: number, y: number): Promise { - await this.sendCommand({ action: "scroll", x, y }); + await this.sendCommand("scroll", { x, y }); } async scrollDown(clicks = 1): Promise { - await this.sendCommand({ action: "scroll_down", clicks }); + await this.sendCommand("scroll_down", { clicks }); } async scrollUp(clicks = 1): Promise { - await this.sendCommand({ action: "scroll_up", clicks }); + await this.sendCommand("scroll_up", { clicks }); } // Screen Actions async screenshot(): Promise { - const response = await this.sendCommand({ action: "screenshot" }); - return Buffer.from(response.data as string, "base64"); + const response = await this.sendCommand("screenshot"); + if (!response.image_data) { + throw new Error("Failed to take screenshot"); + } + return Buffer.from(response.image_data as string, "base64"); } async getScreenSize(): Promise { - const response = await this.sendCommand({ action: "get_screen_size" }); - return response.data as ScreenSize; + const response = await this.sendCommand("get_screen_size"); + if (!response.success || !response.size) { + throw new Error("Failed to get screen size"); + } + return response.size as ScreenSize; } async getCursorPosition(): Promise { - const response = await this.sendCommand({ action: "get_cursor_position" }); - return response.data as CursorPosition; + const response = await this.sendCommand("get_cursor_position"); + if (!response.success || !response.position) { + throw new Error("Failed to get cursor position"); + } + return response.position as CursorPosition; } // Clipboard Actions async copyToClipboard(): Promise { - const response = await this.sendCommand({ action: "copy_to_clipboard" }); - return response.data as string; + const response = await this.sendCommand("copy_to_clipboard"); + if (!response.success || !response.content) { + throw new Error("Failed to get clipboard content"); + } + return response.content as string; } async setClipboard(text: string): Promise { - await this.sendCommand({ action: "set_clipboard", text }); + await this.sendCommand("set_clipboard", { text }); } // File System Actions async fileExists(path: string): Promise { - const response = await this.sendCommand({ action: "file_exists", path }); - return response.data as boolean; + const response = await this.sendCommand("file_exists", { path }); + return (response.exists as boolean) || false; } async directoryExists(path: string): Promise { - const response = await this.sendCommand({ - action: "directory_exists", - path, - }); - return response.data as boolean; + const response = await this.sendCommand("directory_exists", { path }); + return (response.exists as boolean) || false; } async listDir(path: string): Promise { - const response = await this.sendCommand({ action: "list_dir", path }); - return response.data as string[]; + const response = await this.sendCommand("list_dir", { path }); + if (!response.success) { + throw new Error(response.error as string || "Failed to list directory"); + } + return (response.files as string[]) || []; } async readText(path: string): Promise { - const response = await this.sendCommand({ action: "read_text", path }); - return response.data as string; + const response = await this.sendCommand("read_text", { path }); + if (!response.success) { + throw new Error(response.error as string || "Failed to read file"); + } + return (response.content as string) || ""; } async writeText(path: string, content: string): Promise { - await this.sendCommand({ action: "write_text", path, content }); + const response = await this.sendCommand("write_text", { path, content }); + if (!response.success) { + throw new Error(response.error as string || "Failed to write file"); + } } async readBytes(path: string): Promise { - const response = await this.sendCommand({ action: "read_bytes", path }); - return Buffer.from(response.data as string, "base64"); + const response = await this.sendCommand("read_bytes", { path }); + if (!response.success) { + throw new Error(response.error as string || "Failed to read file"); + } + return Buffer.from(response.content_b64 as string, "base64"); } async writeBytes(path: string, content: Buffer): Promise { - await this.sendCommand({ - action: "write_bytes", + const response = await this.sendCommand("write_bytes", { path, - content: content.toString("base64"), + content_b64: content.toString("base64"), }); + if (!response.success) { + throw new Error(response.error as string || "Failed to write file"); + } } async deleteFile(path: string): Promise { - await this.sendCommand({ action: "delete_file", path }); + const response = await this.sendCommand("delete_file", { path }); + if (!response.success) { + throw new Error(response.error as string || "Failed to delete file"); + } } async createDir(path: string): Promise { - await this.sendCommand({ action: "create_dir", path }); + const response = await this.sendCommand("create_dir", { path }); + if (!response.success) { + throw new Error(response.error as string || "Failed to create directory"); + } } async deleteDir(path: string): Promise { - await this.sendCommand({ action: "delete_dir", path }); + const response = await this.sendCommand("delete_dir", { path }); + if (!response.success) { + throw new Error(response.error as string || "Failed to delete directory"); + } } async runCommand(command: string): Promise<[string, string]> { - const response = await this.sendCommand({ action: "run_command", command }); - const data = response.data as { stdout: string; stderr: string }; - return [data.stdout, data.stderr]; + const response = await this.sendCommand("run_command", { command }); + if (!response.success) { + throw new Error(response.error as string || "Failed to run command"); + } + return [(response.stdout as string) || "", (response.stderr as string) || ""]; } // Accessibility Actions async getAccessibilityTree(): Promise { - const response = await this.sendCommand({ - action: "get_accessibility_tree", - }); - return response.data as AccessibilityNode; + const response = await this.sendCommand("get_accessibility_tree"); + if (!response.success) { + throw new Error(response.error as string || "Failed to get accessibility tree"); + } + return response as unknown as AccessibilityNode; } async toScreenCoordinates(x: number, y: number): Promise<[number, number]> { - const response = await this.sendCommand({ - action: "to_screen_coordinates", - x, - y, - }); - return response.data as [number, number]; + const response = await this.sendCommand("to_screen_coordinates", { x, y }); + if (!response.success || !response.coordinates) { + throw new Error("Failed to convert to screen coordinates"); + } + return response.coordinates as [number, number]; } async toScreenshotCoordinates( x: number, y: number ): Promise<[number, number]> { - const response = await this.sendCommand({ - action: "to_screenshot_coordinates", + const response = await this.sendCommand("to_screenshot_coordinates", { x, y, }); - return response.data as [number, number]; + if (!response.success || !response.coordinates) { + throw new Error("Failed to convert to screenshot coordinates"); + } + return response.coordinates as [number, number]; } } From 8be7dafa3e7bbe08068e836e724d13f809d8e55a Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Fri, 20 Jun 2025 11:05:27 -0700 Subject: [PATCH 084/141] Update tests for expected server responses --- .../typescript/tests/interface/macos.test.ts | 301 ++++++++++++------ 1 file changed, 198 insertions(+), 103 deletions(-) diff --git a/libs/computer/typescript/tests/interface/macos.test.ts b/libs/computer/typescript/tests/interface/macos.test.ts index 78d2d9d3..68b03583 100644 --- a/libs/computer/typescript/tests/interface/macos.test.ts +++ b/libs/computer/typescript/tests/interface/macos.test.ts @@ -56,54 +56,83 @@ describe("MacOSComputerInterface", () => { receivedMessages.push(message); // Send appropriate responses based on action - switch (message.action) { + switch (message.command) { case "screenshot": ws.send(JSON.stringify({ - data: Buffer.from("fake-screenshot-data").toString("base64") + image_data: Buffer.from("fake-screenshot-data").toString("base64"), + success: true })); break; case "get_screen_size": - ws.send(JSON.stringify({ data: { width: 1920, height: 1080 } })); + ws.send(JSON.stringify({ + size: { width: 1920, height: 1080 }, + success: true + })); break; case "get_cursor_position": - ws.send(JSON.stringify({ data: { x: 100, y: 200 } })); + ws.send(JSON.stringify({ + position: { x: 100, y: 200 }, + success: true + })); break; case "copy_to_clipboard": - ws.send(JSON.stringify({ data: "clipboard content" })); + ws.send(JSON.stringify({ + content: "clipboard content", + success: true + })); break; case "file_exists": - ws.send(JSON.stringify({ data: true })); + ws.send(JSON.stringify({ + exists: true, + success: true + })); break; case "directory_exists": - ws.send(JSON.stringify({ data: true })); + ws.send(JSON.stringify({ + exists: true, + success: true + })); break; case "list_dir": - ws.send(JSON.stringify({ data: ["file1.txt", "file2.txt"] })); + ws.send(JSON.stringify({ + files: ["file1.txt", "file2.txt"], + success: true + })); break; case "read_text": - ws.send(JSON.stringify({ data: "file content" })); + ws.send(JSON.stringify({ + content: "file content", + success: true + })); break; case "read_bytes": ws.send(JSON.stringify({ - data: Buffer.from("binary content").toString("base64") + content_b64: Buffer.from("binary content").toString("base64"), + success: true })); break; case "run_command": - ws.send(JSON.stringify({ data: { stdout: "command output", stderr: "" } })); + ws.send(JSON.stringify({ + stdout: "command output", + stderr: "", + success: true + })); break; case "get_accessibility_tree": ws.send(JSON.stringify({ - data: { - role: "window", - title: "Test Window", - bounds: { x: 0, y: 0, width: 1920, height: 1080 }, - children: [] - } + role: "window", + title: "Test Window", + bounds: { x: 0, y: 0, width: 1920, height: 1080 }, + children: [], + success: true })); break; case "to_screen_coordinates": case "to_screenshot_coordinates": - ws.send(JSON.stringify({ data: [message.x || 0, message.y || 0] })); + ws.send(JSON.stringify({ + coordinates: [message.params?.x || 0, message.params?.y || 0], + success: true + })); break; default: // For all other actions, just send success @@ -212,10 +241,12 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "mouse_down", - x: 100, - y: 200, - button: "left" + command: "mouse_down", + params: { + x: 100, + y: 200, + button: "left" + } }); }); @@ -224,10 +255,12 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "mouse_up", - x: 100, - y: 200, - button: "right" + command: "mouse_up", + params: { + x: 100, + y: 200, + button: "right" + } }); }); @@ -236,9 +269,11 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "left_click", - x: 150, - y: 250 + command: "left_click", + params: { + x: 150, + y: 250 + } }); }); @@ -247,9 +282,11 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "right_click", - x: 200, - y: 300 + command: "right_click", + params: { + x: 200, + y: 300 + } }); }); @@ -258,9 +295,11 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "double_click", - x: 250, - y: 350 + command: "double_click", + params: { + x: 250, + y: 350 + } }); }); @@ -269,9 +308,11 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "move_cursor", - x: 300, - y: 400 + command: "move_cursor", + params: { + x: 300, + y: 400 + } }); }); @@ -280,11 +321,13 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "drag_to", - x: 400, - y: 500, - button: "left", - duration: 1.5 + command: "drag_to", + params: { + x: 400, + y: 500, + button: "left", + duration: 1.5 + } }); }); @@ -294,10 +337,12 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "drag", - path: path, - button: "middle", - duration: 2.0 + command: "drag", + params: { + path: path, + button: "middle", + duration: 2.0 + } }); }); }); @@ -328,8 +373,10 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "key_down", - key: "a" + command: "key_down", + params: { + key: "a" + } }); }); @@ -338,8 +385,10 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "key_up", - key: "b" + command: "key_up", + params: { + key: "b" + } }); }); @@ -348,8 +397,10 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "type_text", - text: "Hello, World!" + command: "type_text", + params: { + text: "Hello, World!" + } }); }); @@ -358,8 +409,10 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "press_key", - key: "enter" + command: "press_key", + params: { + key: "enter" + } }); }); @@ -368,8 +421,10 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "hotkey", - keys: ["cmd", "c"] + command: "hotkey", + params: { + keys: ["cmd", "c"] + } }); }); }); @@ -400,9 +455,11 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "scroll", - x: 10, - y: -5 + command: "scroll", + params: { + x: 10, + y: -5 + } }); }); @@ -411,8 +468,10 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "scroll_down", - clicks: 3 + command: "scroll_down", + params: { + clicks: 3 + } }); }); @@ -421,8 +480,10 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "scroll_up", - clicks: 2 + command: "scroll_up", + params: { + clicks: 2 + } }); }); }); @@ -456,7 +517,8 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "screenshot" + command: "screenshot", + params: {} }); }); @@ -467,7 +529,8 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "get_screen_size" + command: "get_screen_size", + params: {} }); }); @@ -478,7 +541,8 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "get_cursor_position" + command: "get_cursor_position", + params: {} }); }); }); @@ -511,7 +575,8 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "copy_to_clipboard" + command: "copy_to_clipboard", + params: {} }); }); @@ -520,8 +585,10 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "set_clipboard", - text: "new clipboard text" + command: "set_clipboard", + params: { + text: "new clipboard text" + } }); }); }); @@ -554,8 +621,10 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "file_exists", - path: "/path/to/file" + command: "file_exists", + params: { + path: "/path/to/file" + } }); }); @@ -566,8 +635,10 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "directory_exists", - path: "/path/to/dir" + command: "directory_exists", + params: { + path: "/path/to/dir" + } }); }); @@ -578,8 +649,10 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "list_dir", - path: "/path/to/dir" + command: "list_dir", + params: { + path: "/path/to/dir" + } }); }); @@ -590,8 +663,10 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "read_text", - path: "/path/to/file.txt" + command: "read_text", + params: { + path: "/path/to/file.txt" + } }); }); @@ -600,9 +675,11 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "write_text", - path: "/path/to/file.txt", - content: "new content" + command: "write_text", + params: { + path: "/path/to/file.txt", + content: "new content" + } }); }); @@ -614,8 +691,10 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "read_bytes", - path: "/path/to/file.bin" + command: "read_bytes", + params: { + path: "/path/to/file.bin" + } }); }); @@ -625,9 +704,11 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "write_bytes", - path: "/path/to/file.bin", - content: buffer.toString("base64") + command: "write_bytes", + params: { + path: "/path/to/file.bin", + content_b64: buffer.toString("base64") + } }); }); @@ -636,8 +717,10 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "delete_file", - path: "/path/to/file" + command: "delete_file", + params: { + path: "/path/to/file" + } }); }); @@ -646,8 +729,10 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "create_dir", - path: "/path/to/new/dir" + command: "create_dir", + params: { + path: "/path/to/new/dir" + } }); }); @@ -656,8 +741,10 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "delete_dir", - path: "/path/to/dir" + command: "delete_dir", + params: { + path: "/path/to/dir" + } }); }); @@ -669,8 +756,10 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "run_command", - command: "ls -la" + command: "run_command", + params: { + command: "ls -la" + } }); }); }); @@ -703,12 +792,14 @@ describe("MacOSComputerInterface", () => { role: "window", title: "Test Window", bounds: { x: 0, y: 0, width: 1920, height: 1080 }, - children: [] + children: [], + success: true }); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "get_accessibility_tree" + command: "get_accessibility_tree", + params: {} }); }); @@ -720,9 +811,11 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "to_screen_coordinates", - x: 100, - y: 200 + command: "to_screen_coordinates", + params: { + x: 100, + y: 200 + } }); }); @@ -734,9 +827,11 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - action: "to_screenshot_coordinates", - x: 300, - y: 400 + command: "to_screenshot_coordinates", + params: { + x: 300, + y: 400 + } }); }); }); @@ -764,7 +859,7 @@ describe("MacOSComputerInterface", () => { errorWss.on("connection", (ws) => { ws.on("message", () => { - ws.send(JSON.stringify({ error: "Command failed" })); + ws.send(JSON.stringify({ error: "Command failed", success: false })); }); }); From 3e6ef652459a407133c6db47b69072edcee09f6b Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Fri, 20 Jun 2025 12:18:52 -0700 Subject: [PATCH 085/141] Fix issues with connecting to cloud vms, update tests --- .../src/computer/providers/cloud.ts | 9 +- .../computer/typescript/src/interface/base.ts | 46 +- .../typescript/tests/interface/macos.test.ts | 444 +++++++++--------- 3 files changed, 270 insertions(+), 229 deletions(-) diff --git a/libs/computer/typescript/src/computer/providers/cloud.ts b/libs/computer/typescript/src/computer/providers/cloud.ts index 7e8df495..a1e458dc 100644 --- a/libs/computer/typescript/src/computer/providers/cloud.ts +++ b/libs/computer/typescript/src/computer/providers/cloud.ts @@ -23,8 +23,7 @@ export class CloudComputer extends BaseComputer { } get ip() { - return "192.168.64.9"; - //return `${this.name}.containers.cloud.trycua.com`; + return `${this.name}.containers.cloud.trycua.com`; } /** @@ -36,8 +35,6 @@ export class CloudComputer extends BaseComputer { return; } - logger.info("Starting cloud computer..."); - try { // For cloud provider, the VM is already running, we just need to connect const ipAddress = this.ip; @@ -67,7 +64,7 @@ export class CloudComputer extends BaseComputer { * Stop the cloud computer (disconnect interface) */ async stop(): Promise { - logger.info("Stopping cloud computer..."); + logger.info("Disconnecting from cloud computer..."); if (this.iface) { this.iface.disconnect(); @@ -75,7 +72,7 @@ export class CloudComputer extends BaseComputer { } this.initialized = false; - logger.info("Cloud computer stopped"); + logger.info("Disconnected from cloud computer"); } /** diff --git a/libs/computer/typescript/src/interface/base.ts b/libs/computer/typescript/src/interface/base.ts index 6f012fe6..a69002d4 100644 --- a/libs/computer/typescript/src/interface/base.ts +++ b/libs/computer/typescript/src/interface/base.ts @@ -39,7 +39,6 @@ export abstract class BaseComputerInterface { protected ws: WebSocket; protected apiKey?: string; protected vmName?: string; - protected secure?: boolean; protected logger = pino({ name: "interface-base" }); @@ -48,15 +47,13 @@ export abstract class BaseComputerInterface { username = "lume", password = "lume", apiKey?: string, - vmName?: string, - secure?: boolean + vmName?: string ) { this.ipAddress = ipAddress; this.username = username; this.password = password; this.apiKey = apiKey; this.vmName = vmName; - this.secure = secure; // Initialize WebSocket with headers if needed const headers: { [key: string]: string } = {}; @@ -74,7 +71,7 @@ export abstract class BaseComputerInterface { * Subclasses can override this to customize the URI. */ protected get wsUri(): string { - const protocol = this.secure ? "wss" : "ws"; + const protocol = this.apiKey ? "wss" : "ws"; // Check if ipAddress already includes a port if (this.ipAddress.includes(":")) { @@ -82,7 +79,7 @@ export abstract class BaseComputerInterface { } // Otherwise, append the default port - const port = this.secure ? "8443" : "8000"; + const port = this.apiKey ? "8443" : "8000"; return `${protocol}://${this.ipAddress}:${port}/ws`; } @@ -99,6 +96,7 @@ export abstract class BaseComputerInterface { await this.connect(); return; } catch (error) { + console.log(error); // Wait a bit before retrying this.logger.error( `Error connecting to websocket: ${JSON.stringify(error)}` @@ -115,6 +113,42 @@ export abstract class BaseComputerInterface { */ public async connect(): Promise { if (this.ws.readyState === WebSocket.OPEN) { + // send authentication message if needed + if (this.apiKey && this.vmName) { + this.logger.info("Performing authentication handshake..."); + const authMessage = { + command: "authenticate", + params: { + api_key: this.apiKey, + container_name: this.vmName, + }, + }; + + return new Promise((resolve, reject) => { + const authHandler = (data: WebSocket.RawData) => { + try { + const authResult = JSON.parse(data.toString()); + if (!authResult.success) { + const errorMsg = authResult.error || "Authentication failed"; + this.logger.error(`Authentication failed: ${errorMsg}`); + this.ws.close(); + reject(new Error(`Authentication failed: ${errorMsg}`)); + } else { + this.logger.info("Authentication successful"); + this.ws.off("message", authHandler); + resolve(); + } + } catch (error) { + this.ws.off("message", authHandler); + reject(error); + } + }; + + this.ws.on("message", authHandler); + this.ws.send(JSON.stringify(authMessage)); + }); + } + return; } diff --git a/libs/computer/typescript/tests/interface/macos.test.ts b/libs/computer/typescript/tests/interface/macos.test.ts index 68b03583..72ee07ae 100644 --- a/libs/computer/typescript/tests/interface/macos.test.ts +++ b/libs/computer/typescript/tests/interface/macos.test.ts @@ -8,7 +8,7 @@ describe("MacOSComputerInterface", () => { ipAddress: "localhost", username: "testuser", password: "testpass", - apiKey: "test-api-key", + // apiKey: "test-api-key", No API Key for local testing vmName: "test-vm", }; @@ -37,18 +37,9 @@ describe("MacOSComputerInterface", () => { testParams.ipAddress = `localhost:${serverPort}`; // Handle WebSocket connections - wss.on("connection", (ws, req) => { + wss.on("connection", (ws) => { connectedClients.push(ws); - // Verify authentication headers - const apiKey = req.headers["x-api-key"]; - const vmName = req.headers["x-vm-name"]; - - if (apiKey !== testParams.apiKey || vmName !== testParams.vmName) { - ws.close(1008, "Unauthorized"); - return; - } - // Handle incoming messages ws.on("message", (data) => { try { @@ -58,81 +49,107 @@ describe("MacOSComputerInterface", () => { // Send appropriate responses based on action switch (message.command) { case "screenshot": - ws.send(JSON.stringify({ - image_data: Buffer.from("fake-screenshot-data").toString("base64"), - success: true - })); + ws.send( + JSON.stringify({ + image_data: Buffer.from("fake-screenshot-data").toString( + "base64" + ), + success: true, + }) + ); break; case "get_screen_size": - ws.send(JSON.stringify({ - size: { width: 1920, height: 1080 }, - success: true - })); + ws.send( + JSON.stringify({ + size: { width: 1920, height: 1080 }, + success: true, + }) + ); break; case "get_cursor_position": - ws.send(JSON.stringify({ - position: { x: 100, y: 200 }, - success: true - })); + ws.send( + JSON.stringify({ + position: { x: 100, y: 200 }, + success: true, + }) + ); break; case "copy_to_clipboard": - ws.send(JSON.stringify({ - content: "clipboard content", - success: true - })); + ws.send( + JSON.stringify({ + content: "clipboard content", + success: true, + }) + ); break; case "file_exists": - ws.send(JSON.stringify({ - exists: true, - success: true - })); + ws.send( + JSON.stringify({ + exists: true, + success: true, + }) + ); break; case "directory_exists": - ws.send(JSON.stringify({ - exists: true, - success: true - })); + ws.send( + JSON.stringify({ + exists: true, + success: true, + }) + ); break; case "list_dir": - ws.send(JSON.stringify({ - files: ["file1.txt", "file2.txt"], - success: true - })); + ws.send( + JSON.stringify({ + files: ["file1.txt", "file2.txt"], + success: true, + }) + ); break; case "read_text": - ws.send(JSON.stringify({ - content: "file content", - success: true - })); + ws.send( + JSON.stringify({ + content: "file content", + success: true, + }) + ); break; case "read_bytes": - ws.send(JSON.stringify({ - content_b64: Buffer.from("binary content").toString("base64"), - success: true - })); + ws.send( + JSON.stringify({ + content_b64: Buffer.from("binary content").toString("base64"), + success: true, + }) + ); break; case "run_command": - ws.send(JSON.stringify({ - stdout: "command output", - stderr: "", - success: true - })); + ws.send( + JSON.stringify({ + stdout: "command output", + stderr: "", + success: true, + }) + ); break; case "get_accessibility_tree": - ws.send(JSON.stringify({ - role: "window", - title: "Test Window", - bounds: { x: 0, y: 0, width: 1920, height: 1080 }, - children: [], - success: true - })); + ws.send( + JSON.stringify({ + role: "window", + title: "Test Window", + bounds: { x: 0, y: 0, width: 1920, height: 1080 }, + children: [], + success: true, + }) + ); break; case "to_screen_coordinates": case "to_screenshot_coordinates": - ws.send(JSON.stringify({ - coordinates: [message.params?.x || 0, message.params?.y || 0], - success: true - })); + ws.send( + JSON.stringify({ + coordinates: [message.params?.x || 0, message.params?.y || 0], + success: true, + }) + ); break; default: // For all other actions, just send success @@ -171,9 +188,8 @@ describe("MacOSComputerInterface", () => { testParams.ipAddress, testParams.username, testParams.password, - testParams.apiKey, - testParams.vmName, - false + undefined, + testParams.vmName ); await macosInterface.connect(); @@ -201,8 +217,7 @@ describe("MacOSComputerInterface", () => { testParams.username, testParams.password, undefined, - undefined, - false + undefined ); await macosInterface.connect(); @@ -223,9 +238,8 @@ describe("MacOSComputerInterface", () => { testParams.ipAddress, testParams.username, testParams.password, - testParams.apiKey, - testParams.vmName, - false + undefined, + testParams.vmName ); await macosInterface.connect(); }); @@ -238,87 +252,87 @@ describe("MacOSComputerInterface", () => { it("should send mouse_down command", async () => { await macosInterface.mouseDown(100, 200, "left"); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "mouse_down", params: { x: 100, y: 200, - button: "left" - } + button: "left", + }, }); }); it("should send mouse_up command", async () => { await macosInterface.mouseUp(100, 200, "right"); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "mouse_up", params: { x: 100, y: 200, - button: "right" - } + button: "right", + }, }); }); it("should send left_click command", async () => { await macosInterface.leftClick(150, 250); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "left_click", params: { x: 150, - y: 250 - } + y: 250, + }, }); }); it("should send right_click command", async () => { await macosInterface.rightClick(200, 300); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "right_click", params: { x: 200, - y: 300 - } + y: 300, + }, }); }); it("should send double_click command", async () => { await macosInterface.doubleClick(250, 350); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "double_click", params: { x: 250, - y: 350 - } + y: 350, + }, }); }); it("should send move_cursor command", async () => { await macosInterface.moveCursor(300, 400); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "move_cursor", params: { x: 300, - y: 400 - } + y: 400, + }, }); }); it("should send drag_to command", async () => { await macosInterface.dragTo(400, 500, "left", 1.5); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "drag_to", @@ -326,23 +340,27 @@ describe("MacOSComputerInterface", () => { x: 400, y: 500, button: "left", - duration: 1.5 - } + duration: 1.5, + }, }); }); it("should send drag command with path", async () => { - const path: Array<[number, number]> = [[100, 100], [200, 200], [300, 300]]; + const path: Array<[number, number]> = [ + [100, 100], + [200, 200], + [300, 300], + ]; await macosInterface.drag(path, "middle", 2.0); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "drag", params: { path: path, button: "middle", - duration: 2.0 - } + duration: 2.0, + }, }); }); }); @@ -355,9 +373,8 @@ describe("MacOSComputerInterface", () => { testParams.ipAddress, testParams.username, testParams.password, - testParams.apiKey, - testParams.vmName, - false + undefined, + testParams.vmName ); await macosInterface.connect(); }); @@ -370,61 +387,61 @@ describe("MacOSComputerInterface", () => { it("should send key_down command", async () => { await macosInterface.keyDown("a"); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "key_down", params: { - key: "a" - } + key: "a", + }, }); }); it("should send key_up command", async () => { await macosInterface.keyUp("b"); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "key_up", params: { - key: "b" - } + key: "b", + }, }); }); it("should send type_text command", async () => { await macosInterface.typeText("Hello, World!"); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "type_text", params: { - text: "Hello, World!" - } + text: "Hello, World!", + }, }); }); it("should send press_key command", async () => { await macosInterface.pressKey("enter"); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "press_key", params: { - key: "enter" - } + key: "enter", + }, }); }); it("should send hotkey command", async () => { await macosInterface.hotkey("cmd", "c"); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "hotkey", params: { - keys: ["cmd", "c"] - } + keys: ["cmd", "c"], + }, }); }); }); @@ -437,9 +454,8 @@ describe("MacOSComputerInterface", () => { testParams.ipAddress, testParams.username, testParams.password, - testParams.apiKey, - testParams.vmName, - false + undefined, + testParams.vmName ); await macosInterface.connect(); }); @@ -452,38 +468,38 @@ describe("MacOSComputerInterface", () => { it("should send scroll command", async () => { await macosInterface.scroll(10, -5); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "scroll", params: { x: 10, - y: -5 - } + y: -5, + }, }); }); it("should send scroll_down command", async () => { await macosInterface.scrollDown(3); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "scroll_down", params: { - clicks: 3 - } + clicks: 3, + }, }); }); it("should send scroll_up command", async () => { await macosInterface.scrollUp(2); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "scroll_up", params: { - clicks: 2 - } + clicks: 2, + }, }); }); }); @@ -496,9 +512,8 @@ describe("MacOSComputerInterface", () => { testParams.ipAddress, testParams.username, testParams.password, - testParams.apiKey, - testParams.vmName, - false + undefined, + testParams.vmName ); await macosInterface.connect(); }); @@ -511,38 +526,38 @@ describe("MacOSComputerInterface", () => { it("should get screenshot", async () => { const screenshot = await macosInterface.screenshot(); - + expect(screenshot).toBeInstanceOf(Buffer); expect(screenshot.toString()).toBe("fake-screenshot-data"); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "screenshot", - params: {} + params: {}, }); }); it("should get screen size", async () => { const size = await macosInterface.getScreenSize(); - + expect(size).toEqual({ width: 1920, height: 1080 }); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "get_screen_size", - params: {} + params: {}, }); }); it("should get cursor position", async () => { const position = await macosInterface.getCursorPosition(); - + expect(position).toEqual({ x: 100, y: 200 }); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "get_cursor_position", - params: {} + params: {}, }); }); }); @@ -555,9 +570,8 @@ describe("MacOSComputerInterface", () => { testParams.ipAddress, testParams.username, testParams.password, - testParams.apiKey, - testParams.vmName, - false + undefined, + testParams.vmName ); await macosInterface.connect(); }); @@ -570,25 +584,25 @@ describe("MacOSComputerInterface", () => { it("should copy to clipboard", async () => { const text = await macosInterface.copyToClipboard(); - + expect(text).toBe("clipboard content"); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "copy_to_clipboard", - params: {} + params: {}, }); }); it("should set clipboard", async () => { await macosInterface.setClipboard("new clipboard text"); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "set_clipboard", params: { - text: "new clipboard text" - } + text: "new clipboard text", + }, }); }); }); @@ -601,9 +615,8 @@ describe("MacOSComputerInterface", () => { testParams.ipAddress, testParams.username, testParams.password, - testParams.apiKey, - testParams.vmName, - false + undefined, + testParams.vmName ); await macosInterface.connect(); }); @@ -616,150 +629,150 @@ describe("MacOSComputerInterface", () => { it("should check file exists", async () => { const exists = await macosInterface.fileExists("/path/to/file"); - + expect(exists).toBe(true); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "file_exists", params: { - path: "/path/to/file" - } + path: "/path/to/file", + }, }); }); it("should check directory exists", async () => { const exists = await macosInterface.directoryExists("/path/to/dir"); - + expect(exists).toBe(true); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "directory_exists", params: { - path: "/path/to/dir" - } + path: "/path/to/dir", + }, }); }); it("should list directory", async () => { const files = await macosInterface.listDir("/path/to/dir"); - + expect(files).toEqual(["file1.txt", "file2.txt"]); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "list_dir", params: { - path: "/path/to/dir" - } + path: "/path/to/dir", + }, }); }); it("should read text file", async () => { const content = await macosInterface.readText("/path/to/file.txt"); - + expect(content).toBe("file content"); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "read_text", params: { - path: "/path/to/file.txt" - } + path: "/path/to/file.txt", + }, }); }); it("should write text file", async () => { await macosInterface.writeText("/path/to/file.txt", "new content"); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "write_text", params: { path: "/path/to/file.txt", - content: "new content" - } + content: "new content", + }, }); }); it("should read binary file", async () => { const content = await macosInterface.readBytes("/path/to/file.bin"); - + expect(content).toBeInstanceOf(Buffer); expect(content.toString()).toBe("binary content"); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "read_bytes", params: { - path: "/path/to/file.bin" - } + path: "/path/to/file.bin", + }, }); }); it("should write binary file", async () => { const buffer = Buffer.from("binary data"); await macosInterface.writeBytes("/path/to/file.bin", buffer); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "write_bytes", params: { path: "/path/to/file.bin", - content_b64: buffer.toString("base64") - } + content_b64: buffer.toString("base64"), + }, }); }); it("should delete file", async () => { await macosInterface.deleteFile("/path/to/file"); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "delete_file", params: { - path: "/path/to/file" - } + path: "/path/to/file", + }, }); }); it("should create directory", async () => { await macosInterface.createDir("/path/to/new/dir"); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "create_dir", params: { - path: "/path/to/new/dir" - } + path: "/path/to/new/dir", + }, }); }); it("should delete directory", async () => { await macosInterface.deleteDir("/path/to/dir"); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "delete_dir", params: { - path: "/path/to/dir" - } + path: "/path/to/dir", + }, }); }); it("should run command", async () => { const [stdout, stderr] = await macosInterface.runCommand("ls -la"); - + expect(stdout).toBe("command output"); expect(stderr).toBe(""); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "run_command", params: { - command: "ls -la" - } + command: "ls -la", + }, }); }); }); @@ -772,9 +785,8 @@ describe("MacOSComputerInterface", () => { testParams.ipAddress, testParams.username, testParams.password, - testParams.apiKey, - testParams.vmName, - false + undefined, + testParams.vmName ); await macosInterface.connect(); }); @@ -787,51 +799,51 @@ describe("MacOSComputerInterface", () => { it("should get accessibility tree", async () => { const tree = await macosInterface.getAccessibilityTree(); - + expect(tree).toEqual({ role: "window", title: "Test Window", bounds: { x: 0, y: 0, width: 1920, height: 1080 }, children: [], - success: true + success: true, }); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "get_accessibility_tree", - params: {} + params: {}, }); }); it("should convert to screen coordinates", async () => { const [x, y] = await macosInterface.toScreenCoordinates(100, 200); - + expect(x).toBe(100); expect(y).toBe(200); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "to_screen_coordinates", params: { x: 100, - y: 200 - } + y: 200, + }, }); }); it("should convert to screenshot coordinates", async () => { const [x, y] = await macosInterface.toScreenshotCoordinates(300, 400); - + expect(x).toBe(300); expect(y).toBe(400); - + const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ command: "to_screenshot_coordinates", params: { x: 300, - y: 400 - } + y: 400, + }, }); }); }); @@ -843,9 +855,8 @@ describe("MacOSComputerInterface", () => { "localhost:9999", testParams.username, testParams.password, - testParams.apiKey, - testParams.vmName, - false + undefined, + testParams.vmName ); // Connection should fail @@ -867,15 +878,16 @@ describe("MacOSComputerInterface", () => { `localhost:${errorPort}`, testParams.username, testParams.password, - testParams.apiKey, - testParams.vmName, - false + undefined, + testParams.vmName ); await macosInterface.connect(); // Command should throw error - await expect(macosInterface.leftClick(100, 100)).rejects.toThrow("Command failed"); + await expect(macosInterface.leftClick(100, 100)).rejects.toThrow( + "Command failed" + ); await macosInterface.disconnect(); await new Promise((resolve) => { @@ -888,9 +900,8 @@ describe("MacOSComputerInterface", () => { testParams.ipAddress, testParams.username, testParams.password, - testParams.apiKey, - testParams.vmName, - false + undefined, + testParams.vmName ); await macosInterface.connect(); @@ -912,9 +923,8 @@ describe("MacOSComputerInterface", () => { testParams.ipAddress, testParams.username, testParams.password, - testParams.apiKey, - testParams.vmName, - false + undefined, + testParams.vmName ); await macosInterface.connect(); From cd3c8f37284628204c876a235e80bead263128e7 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Fri, 20 Jun 2025 12:21:22 -0700 Subject: [PATCH 086/141] Disable workflows for now --- .../typescript/.github/{workflows => _workflows}/release.yml | 0 .../typescript/.github/{workflows => _workflows}/unit-test.yml | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename libs/computer/typescript/.github/{workflows => _workflows}/release.yml (100%) rename libs/computer/typescript/.github/{workflows => _workflows}/unit-test.yml (100%) diff --git a/libs/computer/typescript/.github/workflows/release.yml b/libs/computer/typescript/.github/_workflows/release.yml similarity index 100% rename from libs/computer/typescript/.github/workflows/release.yml rename to libs/computer/typescript/.github/_workflows/release.yml diff --git a/libs/computer/typescript/.github/workflows/unit-test.yml b/libs/computer/typescript/.github/_workflows/unit-test.yml similarity index 100% rename from libs/computer/typescript/.github/workflows/unit-test.yml rename to libs/computer/typescript/.github/_workflows/unit-test.yml From 75cbe0835991cfec55c534abc92a833067b862d9 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Fri, 20 Jun 2025 12:27:07 -0700 Subject: [PATCH 087/141] Remove boilerplate items --- libs/computer/typescript/.github/FUNDING.yml | 1 - libs/computer/typescript/.github/renovate.json5 | 4 ---- 2 files changed, 5 deletions(-) delete mode 100644 libs/computer/typescript/.github/FUNDING.yml delete mode 100644 libs/computer/typescript/.github/renovate.json5 diff --git a/libs/computer/typescript/.github/FUNDING.yml b/libs/computer/typescript/.github/FUNDING.yml deleted file mode 100644 index c4630bc2..00000000 --- a/libs/computer/typescript/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -github: sxzz diff --git a/libs/computer/typescript/.github/renovate.json5 b/libs/computer/typescript/.github/renovate.json5 deleted file mode 100644 index ac1c0dc9..00000000 --- a/libs/computer/typescript/.github/renovate.json5 +++ /dev/null @@ -1,4 +0,0 @@ -{ - extends: ['github>sxzz/renovate-config'], - automerge: true, -} From d37728af8d035af85c3cd64b0a1c39ee58693473 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Fri, 20 Jun 2025 12:38:48 -0700 Subject: [PATCH 088/141] Update deploy script for computer for new python directory --- .github/workflows/publish-computer.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-computer.yml b/.github/workflows/publish-computer.yml index b2a9fb25..52e5c6ab 100644 --- a/.github/workflows/publish-computer.yml +++ b/.github/workflows/publish-computer.yml @@ -59,7 +59,7 @@ jobs: - name: Update dependencies to latest versions id: update-deps run: | - cd libs/computer + cd libs/computer/python # Install required package for PyPI API access pip install requests @@ -124,7 +124,7 @@ jobs: uses: ./.github/workflows/reusable-publish.yml with: package_name: "computer" - package_dir: "libs/computer" + package_dir: "libs/computer/python" version: ${{ needs.prepare.outputs.version }} is_lume_package: false base_package_name: "cua-computer" From 7df8c03d20b449369dfd4b1ade3a6de7e0151e05 Mon Sep 17 00:00:00 2001 From: ddupont <3820588+ddupont808@users.noreply.github.com> Date: Fri, 20 Jun 2025 16:09:44 -0400 Subject: [PATCH 089/141] Update COMPATIBILITY.md --- COMPATIBILITY.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index a00d6e45..a1390381 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -50,7 +50,7 @@ |-------------------|-------------|------|------------|-------|-------| | **playground-docker.sh** | Docker Desktop + WSL2 | ❌ Not supported | ❌ Not supported | ✅ Full | Requires WSL2 | | **Dev Container** | VS Code/WindSurf + Docker + WSL2 | ❌ Not supported | ❌ Not supported | ✅ Full | Requires WSL2 | -| **PyPI packages** | Python 3.12+ | ❌ Not supported | ✅ Limited | ✅ Full | WSL for .sh scripts | +| **PyPI packages** | Python 3.12+ | ❌ Not supported | ✅ Full | ✅ Full | | **Windows Host Requirements:** - Windows 10/11 with WSL2 enabled for shell script execution @@ -69,7 +69,7 @@ |----------|----------|-----------------|------------|-------| | **Lume** | ✅ Full support | ⚠️ Limited support | ⚠️ Limited support | macOS: native; Ubuntu/Linux/Windows: need custom image | | **Cloud** | 🚧 Coming soon | ✅ Full support | 🚧 Coming soon | Currently Ubuntu only, macOS/Windows in development | -| **Winsandbox** | ❌ Not supported | ❌ Not supported | ✅ Windows only | Windows Sandbox environments only | +| **Winsandbox** | ❌ Not supported | ❌ Not supported | ✅ Windows only | Windows 10/11 environments only | --- @@ -83,4 +83,4 @@ | **OpenAI** | ✅ Full support | ✅ Full support | ✅ Full support | Cloud-based API | | **Ollama** | ✅ Full support | ✅ Full support | ✅ Full support | Local model serving | | **OpenAI Compatible** | ✅ Full support | ✅ Full support | ✅ Full support | Any OpenAI-compatible API endpoint | -| **MLX VLM** | ✅ macOS only | ❌ Not supported | ❌ Not supported | Apple Silicon required. PyPI installation only. | \ No newline at end of file +| **MLX VLM** | ✅ macOS only | ❌ Not supported | ❌ Not supported | Apple Silicon required. PyPI installation only. | From d4ef68ca994e2b69110832aaa46d8568e34989d4 Mon Sep 17 00:00:00 2001 From: ddupont <3820588+ddupont808@users.noreply.github.com> Date: Fri, 20 Jun 2025 16:10:08 -0400 Subject: [PATCH 090/141] Update README.md --- .devcontainer/README.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.devcontainer/README.md b/.devcontainer/README.md index ed66c86e..cabc2356 100644 --- a/.devcontainer/README.md +++ b/.devcontainer/README.md @@ -6,12 +6,16 @@ This repository includes a Dev Container configuration that simplifies the devel ![Clipboard-20250611-180809-459](https://github.com/user-attachments/assets/447eaeeb-0eec-4354-9a82-44446e202e06) -1. **Install Dev Containers extension** in VS Code -2. **Clone and open in container**: - - Press `Ctrl+Shift+P` (or `Cmd+Shift+P` on Mac) - - Type "Dev Containers: Clone Repository in Container Volume..." - - Paste the repository URL: `https://github.com/trycua/cua.git` -3. **Hit play**: Once the container builds, you're ready to develop! +1. **Install the Dev Containers extension ([VS Code](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) or [WindSurf](https://docs.windsurf.com/windsurf/advanced#dev-containers-beta))** +2. **Open the repository in the Dev Container:** + - Press `Ctrl+Shift+P` (or `⌘+Shift+P` on macOS) + - Select `Dev Containers: Clone Repository in Container Volume...` and paste the repository URL: `https://github.com/trycua/cua.git` (if not cloned) or `Dev Containers: Open Folder in Container...` (if git cloned). + > **Note**: On WindSurf, the post install hook might not run automatically. If so, run `/bin/bash .devcontainer/post-install.sh` manually. +3. **Open the VS Code workspace:** Once the post-install.sh is done running, open the `.vscode/py.code-workspace` workspace and press ![Open Workspace](https://github.com/user-attachments/assets/923bdd43-8c8f-4060-8d78-75bfa302b48c) +. +4. **Run the Agent UI example:** Click ![Run Agent UI](https://github.com/user-attachments/assets/7a61ef34-4b22-4dab-9864-f86bf83e290b) + to start the Gradio UI. If prompted to install **debugpy (Python Debugger)** to enable remote debugging, select 'Yes' to proceed. +5. **Access the Gradio UI:** The Gradio UI will be available at `http://localhost:7860` and will automatically forward to your host machine. ## What's Included From c382881c696e0309f97e5f03aad3ca50ccb34fa0 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Mon, 23 Jun 2025 11:07:31 -0400 Subject: [PATCH 091/141] Add CommandResult dataclass (#295) --- README.md | 3 ++- libs/computer/computer/interface/base.py | 29 ++++++++++++++++++--- libs/computer/computer/interface/linux.py | 11 +++++--- libs/computer/computer/interface/macos.py | 10 ++++--- libs/computer/computer/interface/windows.py | 10 ++++--- libs/computer/computer/models.py | 12 +++++++++ 6 files changed, 61 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index c07df910..7655e888 100644 --- a/README.md +++ b/README.md @@ -249,7 +249,8 @@ For complete examples, see [computer_examples.py](./examples/computer_examples.p ```python # Shell Actions -await computer.interface.run_command(cmd) # Run shell command +result = await computer.interface.run_command(cmd) # Run shell command +# result.stdout, result.stderr, result.returncode # Mouse Actions await computer.interface.left_click(x, y) # Left click at coordinates diff --git a/libs/computer/computer/interface/base.py b/libs/computer/computer/interface/base.py index 09cc46f2..ec87d50a 100644 --- a/libs/computer/computer/interface/base.py +++ b/libs/computer/computer/interface/base.py @@ -3,7 +3,7 @@ from abc import ABC, abstractmethod from typing import Optional, Dict, Any, Tuple, List from ..logger import Logger, LogLevel -from .models import MouseButton +from ..models import Computer, CommandResult, MouseButton class BaseComputerInterface(ABC): @@ -234,8 +234,31 @@ class BaseComputerInterface(ABC): pass @abstractmethod - async def run_command(self, command: str) -> Tuple[str, str]: - """Run shell command.""" + async def run_command(self, command: str) -> CommandResult: + """Run shell command and return structured result. + + Executes a shell command using subprocess.run with shell=True and check=False. + The command is run in the target environment and captures both stdout and stderr. + + Args: + command (str): The shell command to execute + + Returns: + CommandResult: A structured result containing: + - stdout (str): Standard output from the command + - stderr (str): Standard error from the command + - returncode (int): Exit code from the command (0 indicates success) + + Raises: + RuntimeError: If the command execution fails at the system level + + Example: + result = await interface.run_command("ls -la") + if result.returncode == 0: + print(f"Output: {result.stdout}") + else: + print(f"Error: {result.stderr}, Exit code: {result.returncode}") + """ pass # Accessibility Actions diff --git a/libs/computer/computer/interface/linux.py b/libs/computer/computer/interface/linux.py index 23b542b0..d8f4a6de 100644 --- a/libs/computer/computer/interface/linux.py +++ b/libs/computer/computer/interface/linux.py @@ -9,8 +9,7 @@ import websockets from ..logger import Logger, LogLevel from .base import BaseComputerInterface from ..utils import decode_base64_image, encode_base64_image, bytes_to_image, draw_box, resize_image -from .models import Key, KeyType, MouseButton - +from ..models import Computer, CommandResult, Key, KeyType, MouseButton class LinuxComputerInterface(BaseComputerInterface): """Interface for Linux.""" @@ -616,11 +615,15 @@ class LinuxComputerInterface(BaseComputerInterface): if not result.get("success", False): raise RuntimeError(result.get("error", "Failed to delete directory")) - async def run_command(self, command: str) -> Tuple[str, str]: + async def run_command(self, command: str) -> CommandResult: result = await self._send_command("run_command", {"command": command}) if not result.get("success", False): raise RuntimeError(result.get("error", "Failed to run command")) - return result.get("stdout", ""), result.get("stderr", "") + return CommandResult( + stdout=result.get("stdout", ""), + stderr=result.get("stderr", ""), + returncode=result.get("return_code", 0) + ) # Accessibility Actions async def get_accessibility_tree(self) -> Dict[str, Any]: diff --git a/libs/computer/computer/interface/macos.py b/libs/computer/computer/interface/macos.py index a8821d60..da438ebb 100644 --- a/libs/computer/computer/interface/macos.py +++ b/libs/computer/computer/interface/macos.py @@ -9,7 +9,7 @@ import websockets from ..logger import Logger, LogLevel from .base import BaseComputerInterface from ..utils import decode_base64_image, encode_base64_image, bytes_to_image, draw_box, resize_image -from .models import Key, KeyType, MouseButton +from ..models import Computer, CommandResult, Key, KeyType, MouseButton class MacOSComputerInterface(BaseComputerInterface): @@ -623,11 +623,15 @@ class MacOSComputerInterface(BaseComputerInterface): if not result.get("success", False): raise RuntimeError(result.get("error", "Failed to delete directory")) - async def run_command(self, command: str) -> Tuple[str, str]: + async def run_command(self, command: str) -> CommandResult: result = await self._send_command("run_command", {"command": command}) if not result.get("success", False): raise RuntimeError(result.get("error", "Failed to run command")) - return result.get("stdout", ""), result.get("stderr", "") + return CommandResult( + stdout=result.get("stdout", ""), + stderr=result.get("stderr", ""), + returncode=result.get("return_code", 0) + ) # Accessibility Actions async def get_accessibility_tree(self) -> Dict[str, Any]: diff --git a/libs/computer/computer/interface/windows.py b/libs/computer/computer/interface/windows.py index b88c9138..62038a91 100644 --- a/libs/computer/computer/interface/windows.py +++ b/libs/computer/computer/interface/windows.py @@ -9,7 +9,7 @@ import websockets from ..logger import Logger, LogLevel from .base import BaseComputerInterface from ..utils import decode_base64_image, encode_base64_image, bytes_to_image, draw_box, resize_image -from .models import Key, KeyType, MouseButton +from ..models import Computer, CommandResult, Key, KeyType, MouseButton class WindowsComputerInterface(BaseComputerInterface): @@ -615,11 +615,15 @@ class WindowsComputerInterface(BaseComputerInterface): if not result.get("success", False): raise RuntimeError(result.get("error", "Failed to delete directory")) - async def run_command(self, command: str) -> Tuple[str, str]: + async def run_command(self, command: str) -> CommandResult: result = await self._send_command("run_command", {"command": command}) if not result.get("success", False): raise RuntimeError(result.get("error", "Failed to run command")) - return result.get("stdout", ""), result.get("stderr", "") + return CommandResult( + stdout=result.get("stdout", ""), + stderr=result.get("stderr", ""), + returncode=result.get("return_code", 0) + ) # Accessibility Actions async def get_accessibility_tree(self) -> Dict[str, Any]: diff --git a/libs/computer/computer/models.py b/libs/computer/computer/models.py index 5ead143f..931c21eb 100644 --- a/libs/computer/computer/models.py +++ b/libs/computer/computer/models.py @@ -6,6 +6,18 @@ from typing import Optional, Any, Dict # Import base provider interface from .providers.base import BaseVMProvider +@dataclass +class CommandResult: + """Command result.""" + stdout: str + stderr: str + returncode: int + + def __init__(self, stdout: str, stderr: str, returncode: int): + self.stdout = stdout + self.stderr = stderr + self.returncode = returncode + @dataclass class Display: """Display configuration.""" From 15cfd4d3e4a1d2b4d74607606beaa47b40b3b296 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Mon, 23 Jun 2025 12:32:29 -0400 Subject: [PATCH 092/141] Update dependencies for agent and computer libs --- libs/agent/pyproject.toml | 70 ++++++++++++++++++------------------ libs/computer/pyproject.toml | 12 +++---- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/libs/agent/pyproject.toml b/libs/agent/pyproject.toml index f078a5c9..a87d72b6 100644 --- a/libs/agent/pyproject.toml +++ b/libs/agent/pyproject.toml @@ -11,14 +11,14 @@ authors = [ { name = "TryCua", email = "gh@trycua.com" } ] dependencies = [ - "httpx>=0.27.0,<0.29.0", - "aiohttp>=3.9.3,<4.0.0", + "httpx>=0.27.0", + "aiohttp>=3.9.3", "asyncio", - "anyio>=4.4.1,<5.0.0", - "typing-extensions>=4.12.2,<5.0.0", - "pydantic>=2.6.4,<3.0.0", - "rich>=13.7.1,<14.0.0", - "python-dotenv>=1.0.1,<2.0.0", + "anyio>=4.4.1", + "typing-extensions>=4.12.2", + "pydantic>=2.6.4", + "rich>=13.7.1", + "python-dotenv>=1.0.1", "cua-computer>=0.2.0,<0.3.0", "cua-core>=0.1.0,<0.2.0", "certifi>=2024.2.2" @@ -28,21 +28,21 @@ requires-python = ">=3.11" [project.optional-dependencies] anthropic = [ "anthropic>=0.49.0", - "boto3>=1.35.81,<2.0.0", + "boto3>=1.35.81", ] openai = [ - "openai>=1.14.0,<2.0.0", - "httpx>=0.27.0,<0.29.0", + "openai>=1.14.0", + "httpx>=0.27.0", ] uitars = [ - "httpx>=0.27.0,<0.29.0", + "httpx>=0.27.0", ] uitars-mlx = [ "mlx-vlm>=0.1.27; sys_platform == 'darwin'" ] ui = [ - "gradio>=5.23.3,<6.0.0", - "python-dotenv>=1.0.1,<2.0.0", + "gradio>=5.23.3", + "python-dotenv>=1.0.1", ] som = [ "torch>=2.2.1", @@ -51,12 +51,12 @@ som = [ "transformers>=4.38.2", "cua-som>=0.1.0,<0.2.0", # Include all provider dependencies - "anthropic>=0.46.0,<0.47.0", - "boto3>=1.35.81,<2.0.0", - "openai>=1.14.0,<2.0.0", - "groq>=0.4.0,<0.5.0", - "dashscope>=1.13.0,<2.0.0", - "requests>=2.31.0,<3.0.0" + "anthropic>=0.46.0", + "boto3>=1.35.81", + "openai>=1.14.0", + "groq>=0.4.0", + "dashscope>=1.13.0", + "requests>=2.31.0" ] omni = [ "torch>=2.2.1", @@ -64,13 +64,13 @@ omni = [ "ultralytics>=8.0.0", "transformers>=4.38.2", "cua-som>=0.1.0,<0.2.0", - "anthropic>=0.46.0,<0.47.0", - "boto3>=1.35.81,<2.0.0", - "openai>=1.14.0,<2.0.0", - "groq>=0.4.0,<0.5.0", - "dashscope>=1.13.0,<2.0.0", - "requests>=2.31.0,<3.0.0", - "ollama>=0.4.7,<0.5.0" + "anthropic>=0.46.0", + "boto3>=1.35.81", + "openai>=1.14.0", + "groq>=0.4.0", + "dashscope>=1.13.0", + "requests>=2.31.0", + "ollama>=0.4.7" ] all = [ # Include all optional dependencies @@ -79,15 +79,15 @@ all = [ "ultralytics>=8.0.0", "transformers>=4.38.2", "cua-som>=0.1.0,<0.2.0", - "anthropic>=0.46.0,<0.47.0", - "boto3>=1.35.81,<2.0.0", - "openai>=1.14.0,<2.0.0", - "groq>=0.4.0,<0.5.0", - "dashscope>=1.13.0,<2.0.0", - "requests>=2.31.0,<3.0.0", - "ollama>=0.4.7,<0.5.0", - "gradio>=5.23.3,<6.0.0", - "python-dotenv>=1.0.1,<2.0.0", + "anthropic>=0.46.0", + "boto3>=1.35.81", + "openai>=1.14.0", + "groq>=0.4.0", + "dashscope>=1.13.0", + "requests>=2.31.0", + "ollama>=0.4.7", + "gradio>=5.23.3", + "python-dotenv>=1.0.1", "mlx-vlm>=0.1.27; sys_platform == 'darwin'" ] diff --git a/libs/computer/pyproject.toml b/libs/computer/pyproject.toml index 04bd2dfb..4326eb98 100644 --- a/libs/computer/pyproject.toml +++ b/libs/computer/pyproject.toml @@ -26,15 +26,15 @@ lume = [ lumier = [ ] ui = [ - "gradio>=5.23.3,<6.0.0", - "python-dotenv>=1.0.1,<2.0.0", - "datasets>=3.6.0,<4.0.0", + "gradio>=5.23.3", + "python-dotenv>=1.0.1", + "datasets>=3.6.0", ] all = [ # Include all optional dependencies - "gradio>=5.23.3,<6.0.0", - "python-dotenv>=1.0.1,<2.0.0", - "datasets>=3.6.0,<4.0.0", + "gradio>=5.23.3", + "python-dotenv>=1.0.1", + "datasets>=3.6.0", ] [tool.pdm] From 54d585e174e03912f982dcc7b913dddd0718813d Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Mon, 23 Jun 2025 12:38:25 -0400 Subject: [PATCH 093/141] Added build.ps1 and computer[all] installation step --- scripts/build.ps1 | 157 ++++++++++++++++++++++++++++++++++++++++++++++ scripts/build.sh | 4 +- 2 files changed, 159 insertions(+), 2 deletions(-) create mode 100644 scripts/build.ps1 diff --git a/scripts/build.ps1 b/scripts/build.ps1 new file mode 100644 index 00000000..8c2c99c2 --- /dev/null +++ b/scripts/build.ps1 @@ -0,0 +1,157 @@ +# PowerShell Build Script for CUA +# Exit on error +$ErrorActionPreference = "Stop" + +# Colors for output +$RED = "Red" +$GREEN = "Green" +$BLUE = "Blue" + +# Function to print step information +function Print-Step { + param([string]$Message) + Write-Host "==> $Message" -ForegroundColor $BLUE +} + +# Function to print success message +function Print-Success { + param([string]$Message) + Write-Host "==> Success: $Message" -ForegroundColor $GREEN +} + +# Function to print error message +function Print-Error { + param([string]$Message) + Write-Host "==> Error: $Message" -ForegroundColor $RED +} + +# Get the script's directory and project root +$SCRIPT_DIR = Split-Path -Parent $MyInvocation.MyCommand.Path +$PROJECT_ROOT = Split-Path -Parent $SCRIPT_DIR + +# Change to project root +Set-Location $PROJECT_ROOT + +# Load environment variables from .env.local +if (Test-Path ".env.local") { + Print-Step "Loading environment variables from .env.local..." + Get-Content ".env.local" | ForEach-Object { + if ($_ -match "^([^#][^=]*?)=(.*)$") { + [Environment]::SetEnvironmentVariable($matches[1], $matches[2], "Process") + } + } + Print-Success "Environment variables loaded" +} else { + Print-Error ".env.local file not found" + exit 1 +} + +# Check if conda is available +try { + conda --version | Out-Null + Print-Success "Conda is available" +} catch { + Print-Error "Conda is not available. Please install Anaconda or Miniconda first." + exit 1 +} + +# Create or update conda environment +Print-Step "Creating/updating conda environment 'cua' with Python 3.12..." +try { + # Check if environment exists + $envExists = conda env list | Select-String "^cua\s" + if ($envExists) { + Print-Step "Environment 'cua' already exists. Updating..." + conda env update -n cua -f environment.yml --prune + } else { + Print-Step "Creating new environment 'cua'..." + conda create -n cua python=3.12 -y + } + Print-Success "Conda environment 'cua' ready" +} catch { + Print-Error "Failed to create/update conda environment" + exit 1 +} + +# Activate conda environment +Print-Step "Activating conda environment 'cua'..." +try { + conda activate cua + Print-Success "Environment activated" +} catch { + Print-Error "Failed to activate conda environment 'cua'" + Print-Step "Please run: conda activate cua" + Print-Step "Then re-run this script" + exit 1 +} + +# Clean up existing environments and cache +Print-Step "Cleaning up existing environments..." +Get-ChildItem -Path . -Recurse -Directory -Name "__pycache__" | ForEach-Object { Remove-Item -Path $_ -Recurse -Force } +Get-ChildItem -Path . -Recurse -Directory -Name ".pytest_cache" | ForEach-Object { Remove-Item -Path $_ -Recurse -Force } +Get-ChildItem -Path . -Recurse -Directory -Name "dist" | ForEach-Object { Remove-Item -Path $_ -Recurse -Force } +Get-ChildItem -Path . -Recurse -Directory -Name "*.egg-info" | ForEach-Object { Remove-Item -Path $_ -Recurse -Force } + +# Function to install a package and its dependencies +function Install-Package { + param( + [string]$PackageDir, + [string]$PackageName, + [string]$Extras = "" + ) + + Print-Step "Installing $PackageName..." + Set-Location $PackageDir + + if (Test-Path "pyproject.toml") { + if ($Extras) { + pip install -e ".[$Extras]" + } else { + pip install -e . + } + } else { + Print-Error "No pyproject.toml found in $PackageDir" + Set-Location $PROJECT_ROOT + return $false + } + + Set-Location $PROJECT_ROOT + return $true +} + +# Install packages in order of dependency +Print-Step "Installing packages in development mode..." + +# Install core first (base package with telemetry support) +if (-not (Install-Package "libs/core" "core")) { exit 1 } + +# Install pylume (base dependency) +if (-not (Install-Package "libs/pylume" "pylume")) { exit 1 } + +# Install computer with all its dependencies and extras +if (-not (Install-Package "libs/computer" "computer" "all")) { exit 1 } + +# Install omniparser +if (-not (Install-Package "libs/som" "som")) { exit 1 } + +# Install agent with all its dependencies and extras +if (-not (Install-Package "libs/agent" "agent" "all")) { exit 1 } + +# Install computer-server +if (-not (Install-Package "libs/computer-server" "computer-server")) { exit 1 } + +# Install mcp-server +if (-not (Install-Package "libs/mcp-server" "mcp-server")) { exit 1 } + +# Install development tools from root project +Print-Step "Installing development dependencies..." +pip install -e ".[dev,test,docs]" + +# Create a .env file for VS Code to use the virtual environment +Print-Step "Creating .env file for VS Code..." +$pythonPath = "$PROJECT_ROOT/libs/core;$PROJECT_ROOT/libs/computer;$PROJECT_ROOT/libs/agent;$PROJECT_ROOT/libs/som;$PROJECT_ROOT/libs/pylume;$PROJECT_ROOT/libs/computer-server;$PROJECT_ROOT/libs/mcp-server" +"PYTHONPATH=$pythonPath" | Out-File -FilePath ".env" -Encoding UTF8 + +Print-Success "All packages installed successfully!" +Print-Step "Your conda environment 'cua' is ready. To activate it:" +Write-Host " conda activate cua" -ForegroundColor Yellow diff --git a/scripts/build.sh b/scripts/build.sh index 5d747816..19021423 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -92,8 +92,8 @@ install_package "libs/core" "core" # Install pylume (base dependency) install_package "libs/pylume" "pylume" -# Install computer (depends on pylume) -install_package "libs/computer" "computer" +# Install computer with all its dependencies and extras +install_package "libs/computer" "computer" "all" # Install omniparser install_package "libs/som" "som" From 7f3b9884f6637bcc86f7860fa25f609ef2723ee3 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Mon, 23 Jun 2025 09:55:06 -0700 Subject: [PATCH 094/141] Remove lume from typescript sdk, rename CloudComputer -> Computer --- libs/computer/typescript/README.md | 18 +- libs/computer/typescript/package.json | 1 - libs/computer/typescript/pnpm-lock.yaml | 252 +------- .../computer/typescript/src/computer/index.ts | 2 +- .../src/computer/providers/index.ts | 3 +- .../typescript/src/computer/providers/lume.ts | 278 -------- .../computer/typescript/src/computer/types.ts | 81 --- libs/computer/typescript/src/index.ts | 2 +- libs/computer/typescript/src/util/lume.ts | 592 ------------------ .../typescript/tests/computer/cloud.test.ts | 6 +- 10 files changed, 13 insertions(+), 1222 deletions(-) delete mode 100644 libs/computer/typescript/src/computer/providers/lume.ts delete mode 100644 libs/computer/typescript/src/util/lume.ts diff --git a/libs/computer/typescript/README.md b/libs/computer/typescript/README.md index 77e2c47c..98311be6 100644 --- a/libs/computer/typescript/README.md +++ b/libs/computer/typescript/README.md @@ -1,6 +1,6 @@ -# C/UA Computer TypeScript Library +# C/ua Computer TypeScript Library -The TypeScript library for C/UA Computer - a powerful computer control and automation library. +The TypeScript library for C/cua Computer - a powerful computer control and automation library. ## Overview @@ -20,7 +20,7 @@ pnpm add @cua/computer import { Computer } from '@cua/computer'; // Create a new computer instance -const computer = new CloudComputer({ +const computer = new Computer({ osType: OSType.LINUX, name: 's-linux-vm_id' apiKey: 'your-api-key' @@ -57,13 +57,7 @@ The library is organized into the following structure: ### Provider Implementations -- **LumeComputer**: Implementation for Lume API-based VMs (Unmaintained) -- **CloudComputer**: Implementation for cloud-based VMs - -### Utility Functions - -- **Lume API Utilities**: Functions for interacting with the Lume API (lumeApiGet, lumeApiRun, lumeApiStop, etc.) (Unmaintained) -- **Helper Functions**: Parsing utilities for display and memory strings +- **Computer**: Implementation for cloud-based VMs ## Development @@ -91,10 +85,6 @@ pnpm build pnpm typecheck ``` -## Disclaimer - -**WARNING:** Some parts of this library, particularly the provider implementations (like Lume), were created as test/example implementations and are not maintained or expected to work in production environments. They serve as references for how providers might be implemented but should not be used in production. - ## License [MIT](./LICENSE) License 2025 [C/UA](https://github.com/trycua) diff --git a/libs/computer/typescript/package.json b/libs/computer/typescript/package.json index 71141f22..cd917772 100644 --- a/libs/computer/typescript/package.json +++ b/libs/computer/typescript/package.json @@ -39,7 +39,6 @@ }, "dependencies": { "pino": "^9.7.0", - "sharp": "^0.33.0", "ws": "^8.18.0" }, "devDependencies": { diff --git a/libs/computer/typescript/pnpm-lock.yaml b/libs/computer/typescript/pnpm-lock.yaml index 9104a636..e1b1e8f5 100644 --- a/libs/computer/typescript/pnpm-lock.yaml +++ b/libs/computer/typescript/pnpm-lock.yaml @@ -11,9 +11,6 @@ importers: pino: specifier: ^9.7.0 version: 9.7.0 - sharp: - specifier: ^0.33.0 - version: 0.33.5 ws: specifier: ^8.18.0 version: 8.18.2 @@ -290,111 +287,6 @@ packages: cpu: [x64] os: [win32] - '@img/sharp-darwin-arm64@0.33.5': - resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [darwin] - - '@img/sharp-darwin-x64@0.33.5': - resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-darwin-arm64@1.0.4': - resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} - cpu: [arm64] - os: [darwin] - - '@img/sharp-libvips-darwin-x64@1.0.4': - resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-linux-arm64@1.0.4': - resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} - cpu: [arm64] - os: [linux] - - '@img/sharp-libvips-linux-arm@1.0.5': - resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} - cpu: [arm] - os: [linux] - - '@img/sharp-libvips-linux-s390x@1.0.4': - resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} - cpu: [s390x] - os: [linux] - - '@img/sharp-libvips-linux-x64@1.0.4': - resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} - cpu: [x64] - os: [linux] - - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': - resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} - cpu: [arm64] - os: [linux] - - '@img/sharp-libvips-linuxmusl-x64@1.0.4': - resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} - cpu: [x64] - os: [linux] - - '@img/sharp-linux-arm64@0.33.5': - resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - - '@img/sharp-linux-arm@0.33.5': - resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm] - os: [linux] - - '@img/sharp-linux-s390x@0.33.5': - resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [s390x] - os: [linux] - - '@img/sharp-linux-x64@0.33.5': - resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - - '@img/sharp-linuxmusl-arm64@0.33.5': - resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - - '@img/sharp-linuxmusl-x64@0.33.5': - resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - - '@img/sharp-wasm32@0.33.5': - resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [wasm32] - - '@img/sharp-win32-ia32@0.33.5': - resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ia32] - os: [win32] - - '@img/sharp-win32-x64@0.33.5': - resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [win32] - '@inquirer/confirm@5.1.12': resolution: {integrity: sha512-dpq+ielV9/bqgXRUbNH//KsY6WEw9DrGPmipkpmgC1Y46cwuBTNx7PXFWTjc3MQ+urcc0QxoVHcMI0FW4Ok0hg==} engines: {node: '>=18'} @@ -776,13 +668,6 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - color-string@1.9.1: - resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} - - color@4.2.3: - resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} - engines: {node: '>=12.5.0'} - confbox@0.2.2: resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} @@ -813,10 +698,6 @@ packages: destr@2.0.5: resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} - detect-libc@2.0.4: - resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} - engines: {node: '>=8'} - diff@8.0.2: resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} engines: {node: '>=0.3.1'} @@ -905,9 +786,6 @@ packages: hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} - is-arrayish@0.3.2: - resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} - is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} @@ -1095,10 +973,6 @@ packages: engines: {node: '>=10'} hasBin: true - sharp@0.33.5: - resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -1106,9 +980,6 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - simple-swizzle@0.2.2: - resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} - sonic-boom@4.2.0: resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} @@ -1521,81 +1392,6 @@ snapshots: '@esbuild/win32-x64@0.25.5': optional: true - '@img/sharp-darwin-arm64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.0.4 - optional: true - - '@img/sharp-darwin-x64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.0.4 - optional: true - - '@img/sharp-libvips-darwin-arm64@1.0.4': - optional: true - - '@img/sharp-libvips-darwin-x64@1.0.4': - optional: true - - '@img/sharp-libvips-linux-arm64@1.0.4': - optional: true - - '@img/sharp-libvips-linux-arm@1.0.5': - optional: true - - '@img/sharp-libvips-linux-s390x@1.0.4': - optional: true - - '@img/sharp-libvips-linux-x64@1.0.4': - optional: true - - '@img/sharp-libvips-linuxmusl-arm64@1.0.4': - optional: true - - '@img/sharp-libvips-linuxmusl-x64@1.0.4': - optional: true - - '@img/sharp-linux-arm64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.0.4 - optional: true - - '@img/sharp-linux-arm@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.0.5 - optional: true - - '@img/sharp-linux-s390x@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.0.4 - optional: true - - '@img/sharp-linux-x64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.0.4 - optional: true - - '@img/sharp-linuxmusl-arm64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 - optional: true - - '@img/sharp-linuxmusl-x64@0.33.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 - optional: true - - '@img/sharp-wasm32@0.33.5': - dependencies: - '@emnapi/runtime': 1.4.3 - optional: true - - '@img/sharp-win32-ia32@0.33.5': - optional: true - - '@img/sharp-win32-x64@0.33.5': - optional: true - '@inquirer/confirm@5.1.12(@types/node@22.15.31)': dependencies: '@inquirer/core': 10.1.13(@types/node@22.15.31) @@ -1953,18 +1749,10 @@ snapshots: color-convert@2.0.1: dependencies: color-name: 1.1.4 + optional: true - color-name@1.1.4: {} - - color-string@1.9.1: - dependencies: - color-name: 1.1.4 - simple-swizzle: 0.2.2 - - color@4.2.3: - dependencies: - color-convert: 2.0.1 - color-string: 1.9.1 + color-name@1.1.4: + optional: true confbox@0.2.2: {} @@ -1983,8 +1771,6 @@ snapshots: destr@2.0.5: {} - detect-libc@2.0.4: {} - diff@8.0.2: {} dotenv@16.5.0: {} @@ -2074,8 +1860,6 @@ snapshots: hookable@5.5.3: {} - is-arrayish@0.3.2: {} - is-fullwidth-code-point@3.0.0: optional: true @@ -2293,41 +2077,11 @@ snapshots: semver@7.7.2: {} - sharp@0.33.5: - dependencies: - color: 4.2.3 - detect-libc: 2.0.4 - semver: 7.7.2 - optionalDependencies: - '@img/sharp-darwin-arm64': 0.33.5 - '@img/sharp-darwin-x64': 0.33.5 - '@img/sharp-libvips-darwin-arm64': 1.0.4 - '@img/sharp-libvips-darwin-x64': 1.0.4 - '@img/sharp-libvips-linux-arm': 1.0.5 - '@img/sharp-libvips-linux-arm64': 1.0.4 - '@img/sharp-libvips-linux-s390x': 1.0.4 - '@img/sharp-libvips-linux-x64': 1.0.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 - '@img/sharp-libvips-linuxmusl-x64': 1.0.4 - '@img/sharp-linux-arm': 0.33.5 - '@img/sharp-linux-arm64': 0.33.5 - '@img/sharp-linux-s390x': 0.33.5 - '@img/sharp-linux-x64': 0.33.5 - '@img/sharp-linuxmusl-arm64': 0.33.5 - '@img/sharp-linuxmusl-x64': 0.33.5 - '@img/sharp-wasm32': 0.33.5 - '@img/sharp-win32-ia32': 0.33.5 - '@img/sharp-win32-x64': 0.33.5 - siginfo@2.0.0: {} signal-exit@4.1.0: optional: true - simple-swizzle@0.2.2: - dependencies: - is-arrayish: 0.3.2 - sonic-boom@4.2.0: dependencies: atomic-sleep: 1.0.0 diff --git a/libs/computer/typescript/src/computer/index.ts b/libs/computer/typescript/src/computer/index.ts index 365dd478..4a1a7007 100644 --- a/libs/computer/typescript/src/computer/index.ts +++ b/libs/computer/typescript/src/computer/index.ts @@ -1 +1 @@ -export { BaseComputer, CloudComputer, LumeComputer } from "./providers"; +export { BaseComputer, CloudComputer } from "./providers"; diff --git a/libs/computer/typescript/src/computer/providers/index.ts b/libs/computer/typescript/src/computer/providers/index.ts index 70ff7ae8..d4fd5391 100644 --- a/libs/computer/typescript/src/computer/providers/index.ts +++ b/libs/computer/typescript/src/computer/providers/index.ts @@ -1,3 +1,2 @@ export * from "./base"; -export * from "./cloud"; -export * from "./lume"; +export * from "./cloud"; \ No newline at end of file diff --git a/libs/computer/typescript/src/computer/providers/lume.ts b/libs/computer/typescript/src/computer/providers/lume.ts deleted file mode 100644 index e96f9221..00000000 --- a/libs/computer/typescript/src/computer/providers/lume.ts +++ /dev/null @@ -1,278 +0,0 @@ -/** - * WARNING: This file was created as a test/example implementation and is not maintained or expected to work. - * It serves as a reference for how a provider might be implemented but should not be used in production. - */ - -import type { Display, LumeComputerConfig } from "../types.ts"; -import { BaseComputer } from "./base.ts"; -import { - lumeApiGet, - lumeApiRun, - lumeApiPull, - lumeApiStop, - lumeApiDelete, - lumeApiUpdate, - type VMInfo, -} from "../../util/lume.ts"; -import pino from "pino"; - -const logger = pino({ name: "computer-lume" }); - -/** - * Lume-specific computer implementation - */ -export class LumeComputer extends BaseComputer { - private display: string | Display; - private memory: string; - private cpu: number; - private image: string; - private port: number; - private host: string; - private ephemeral: boolean; - - constructor(config: LumeComputerConfig) { - super(config); - - this.display = config.display ?? "1024x768"; - this.memory = config.memory ?? "8GB"; - this.cpu = config.cpu ?? 2; - this.image = config.image ?? "macos-sequoia-cua:latest"; - this.port = config.port ?? 7777; - this.host = config.host ?? "localhost"; - this.ephemeral = config.ephemeral ?? false; - } - - /** - * Lume-specific method to get a VM - */ - - async getVm(name: string, storage?: string): Promise { - try { - const vmInfo = (await lumeApiGet(name, this.host, this.port, storage))[0]; - if (!vmInfo) throw new Error("VM Not Found."); - if (vmInfo.status === "stopped") { - logger.info( - `VM ${name} is in '${vmInfo.status}' state - not waiting for IP address` - ); - return { - ...vmInfo, - name, - status: vmInfo.status, - }; - } - if (!vmInfo.ipAddress) { - logger.info( - `VM ${name} is in '${vmInfo.status}' state but no IP address found - reporting as still starting` - ); - } - return vmInfo; - } catch (e) { - logger.error(`Failed to get VM status: ${e}`); - throw e; - } - } - - /** - * Lume-specific method to list availalbe VMs - */ - - async listVm() { - const vms = await lumeApiGet("", this.host, this.port); - return vms; - } - - /** - * Lume-specific method to run the VM - */ - async runVm( - image: string, - name: string, - runOpts: { [key: string]: unknown } = {}, - storage?: string - ): Promise { - logger.info( - `Running Lume computer ${this.name} with ${this.memory} memory and ${this.cpu} CPUs` - ); - logger.info( - `Using image ${this.image} with display ${ - typeof this.display === "string" - ? this.display - : `${this.display.width}x${this.display.height}` - }` - ); - // Lume-specific implementation - try { - await this.getVm(name, storage); - } catch { - logger.info( - `VM ${name} not found, attempting to pull image ${image} from registry...` - ); - // Call pull_vm with the image parameter - try { - const pullRes = await this.pullVm(name, image, storage); - logger.info(pullRes); - } catch (e) { - logger.info(`Failed to pull VM image: ${e}`); - throw e; - } - } - logger.info(`Running VM ${name} with options: ${runOpts}`); - return await lumeApiRun(name, this.host, this.port, runOpts, storage); - } - - /** - * Lume-specific method to stop a VM - */ - async stopVm(name: string, storage?: string): Promise { - // Stop the VM first - const stopResult = await lumeApiStop(name, this.host, this.port, storage); - - // If ephemeral mode is enabled, delete the VM after stopping - if (this.ephemeral && (!stopResult || !("error" in stopResult))) { - logger.info( - `Ephemeral mode enabled - deleting VM ${name} after stopping` - ); - try { - const deleteResult = await this.deleteVm(name, storage); - - // Return combined result - return { - ...stopResult, - deleted: true, - deleteResult: deleteResult, - } as VMInfo; - } catch (e) { - logger.error(`Failed to delete ephemeral VM ${name}: ${e}`); - throw new Error(`Failed to delete ephemeral VM ${name}: ${e}`); - } - } - - // Just return the stop result if not ephemeral - return stopResult; - } - - /** - * Lume-specific method to pull a VM image from the registry - */ - async pullVm( - name: string, - image: string, - storage?: string, - registry = "ghcr.io", - organization = "trycua" - ): Promise { - // Validate image parameter - if (!image) { - throw new Error("Image parameter is required for pullVm"); - } - - logger.info(`Pulling VM image '${image}' as '${name}'`); - logger.info("You can check the pull progress using: lume logs -f"); - logger.debug(`Pull storage location: ${storage || "default"}`); - - try { - const result = await lumeApiPull( - image, - name, - this.host, - this.port, - storage, - registry, - organization - ); - - logger.info(`Successfully pulled VM image '${image}' as '${name}'`); - return result; - } catch (e) { - logger.error(`Failed to pull VM image '${image}': ${e}`); - throw new Error(`Failed to pull VM: ${e}`); - } - } - - /** - * Lume-specific method to delete a VM permanently - */ - async deleteVm(name: string, storage?: string): Promise { - logger.info(`Deleting VM ${name}...`); - - try { - const result = await lumeApiDelete( - name, - this.host, - this.port, - storage, - false, - false - ); - - logger.info(`Successfully deleted VM '${name}'`); - return result; - } catch (e) { - logger.error(`Failed to delete VM '${name}': ${e}`); - throw new Error(`Failed to delete VM: ${e}`); - } - } - - /** - * Lume-specific method to update VM configuration - */ - async updateVm( - name: string, - updateOpts: { [key: string]: unknown }, - storage?: string - ): Promise { - return await lumeApiUpdate( - name, - this.host, - this.port, - updateOpts, - storage, - false, - false - ); - } - - /** - * Lume-specific method to get the IP address of a VM, waiting indefinitely until it's available - */ - async getIp(name: string, storage?: string, retryDelay = 2): Promise { - // Track total attempts for logging purposes - let attempts = 0; - - while (true) { - attempts++; - - try { - const vmInfo = await this.getVm(name, storage); - - // Check if VM has an IP address - if (vmInfo.ipAddress) { - logger.info( - `Got IP address for VM ${name} after ${attempts} attempts: ${vmInfo.ipAddress}` - ); - return vmInfo.ipAddress; - } - - // Check if VM is in a state where it won't get an IP - if (vmInfo.status === "stopped" || vmInfo.status === "error") { - throw new Error( - `VM ${name} is in '${vmInfo.status}' state and will not get an IP address` - ); - } - - // Log progress every 10 attempts - if (attempts % 10 === 0) { - logger.info( - `Still waiting for IP address for VM ${name} (${attempts} attempts)...` - ); - } - - // Wait before retrying - await new Promise((resolve) => setTimeout(resolve, retryDelay * 1000)); - } catch (e) { - logger.error(`Error getting IP for VM ${name}: ${e}`); - throw e; - } - } - } -} diff --git a/libs/computer/typescript/src/computer/types.ts b/libs/computer/typescript/src/computer/types.ts index dbf8c734..296302f3 100644 --- a/libs/computer/typescript/src/computer/types.ts +++ b/libs/computer/typescript/src/computer/types.ts @@ -31,87 +31,6 @@ export interface CloudComputerConfig extends BaseComputerConfig { apiKey: string; } -export interface LumeComputerConfig extends BaseComputerConfig { - /** - * The display configuration. Can be: - * - A Display object - * - A dict with 'width' and 'height' - * - A string in format "WIDTHxHEIGHT" (e.g. "1920x1080") - * @default "1024x768" - */ - display?: Display | string; - - /** - * The VM memory allocation. (e.g. "8GB", "4GB", "1024MB") - * @default "8GB" - */ - memory?: string; - - /** - * The VM CPU allocation. - * @default 4 - */ - cpu?: number; - - /** - * The VM image name - * @default "macos-sequoia-cua:latest" - */ - image?: string; - - /** - * Optional list of directory paths to share with the VM - */ - sharedDirectories?: string[]; - - /** - * If True, target localhost instead of starting a VM - * @default false - */ - useHostComputerServer?: boolean; - - /** - * Whether to enable telemetry tracking. - * @default true - */ - telemetryEnabled?: boolean; - - /** - * Optional port to use for the VM provider server - * @default 7777 - */ - port?: number; - - /** - * Optional port for the noVNC web interface - * @default 8006 - */ - noVNCPort?: number; - - /** - * Host to use for VM provider connections (e.g. "localhost", "host.docker.internal") - * @default "localhost" - */ - host?: string; - - /** - * Optional path for persistent VM storage - */ - storage?: string; - - /** - * Whether to use ephemeral storage - * @default false - */ - ephemeral?: boolean; - - /** - * Optional list of experimental features to enable (e.g. ["app-use"]) - */ - experiments?: string[]; -} - export enum VMProviderType { CLOUD = "cloud", - LUME = "lume", } diff --git a/libs/computer/typescript/src/index.ts b/libs/computer/typescript/src/index.ts index ef312139..9917d559 100644 --- a/libs/computer/typescript/src/index.ts +++ b/libs/computer/typescript/src/index.ts @@ -1,5 +1,5 @@ // Export classes -export * from "./computer"; +export { CloudComputer as Computer } from "./computer"; //todo: figure out what types to export and how to do that // diff --git a/libs/computer/typescript/src/util/lume.ts b/libs/computer/typescript/src/util/lume.ts deleted file mode 100644 index cdbd8ce5..00000000 --- a/libs/computer/typescript/src/util/lume.ts +++ /dev/null @@ -1,592 +0,0 @@ -/** - * Shared API utilities for Lume providers. - * - * This module contains shared functions for interacting with the Lume API, - * used by LumeProvider. - * - * WARNING: This file was created as a test/example implementation and is not maintained or expected to work. - * It serves as a reference for how a provider might be implemented but should not be used in production. - */ - -import pino from "pino"; - -// Setup logging -const logger = pino({ name: "lume_api" }); - -// Types for API responses and options -// These are lume-specific -export interface SharedDirectory { - hostPath: string; - tag: string; - readOnly: boolean; -} - -export interface VMInfo { - status?: string; - name: string; - diskSize: { - allocated: number; - total: number; - }; - memorySize: number; - os: string; - display: string; - locationName: string; - cpuCount?: number; - // started state results - vncUrl?: string; - ipAddress?: string; - sharedDirectories?: SharedDirectory[]; -} - -export interface RunOptions { - [key: string]: unknown; -} - -export interface UpdateOptions { - [key: string]: unknown; -} - -/** - * Use fetch to get VM information from Lume API. - * - * @param vmName - Name of the VM to get info for - * @param host - API host - * @param port - API port - * @param storage - Storage path for the VM - * @param debug - Whether to show debug output - * @param verbose - Enable verbose logging - * @returns Dictionary with VM status information parsed from JSON response - */ -export async function lumeApiGet( - vmName: string, - host: string, - port: number, - storage?: string, - debug = false, - verbose = false -): Promise { - // URL encode the storage parameter for the query - let storageParam = ""; - - if (storage) { - // First encode the storage path properly - const encodedStorage = encodeURIComponent(storage); - storageParam = `?storage=${encodedStorage}`; - } - - // Construct API URL with encoded storage parameter if needed - const apiUrl = `http://${host}:${port}/lume/vms${ - vmName ? `/${vmName}` : "" - }${storageParam}`; - - // Only print the fetch URL when debug is enabled - logger.info(`Executing API request: ${apiUrl}`); - - try { - // Execute the request with timeouts - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 20000); // 20 second timeout - - const response = await fetch(apiUrl, { - method: "GET", - signal: controller.signal, - }); - - clearTimeout(timeoutId); - - if (!response.ok) { - // Handle HTTP errors - const errorMsg = `HTTP error returned from API server (status: ${response.status})`; - throw new Error(`API request failed: ${errorMsg}`); - } - - // Parse JSON response - // If vmName is provided, API returns a single object; otherwise it returns an array - const data = await response.json(); - const result = vmName ? [data as VMInfo] : (data as VMInfo[]); - - if (debug || verbose) { - console.log(`DEBUG: API response: ${JSON.stringify(result, null, 2)}`); - } - - return result; - } catch (err) { - const error = err as Error; - let errorMsg = "Unknown error"; - - if (error.name === "AbortError") { - errorMsg = - "Operation timeout - the API server is taking too long to respond"; - } else if (error.message.includes("ECONNREFUSED")) { - errorMsg = - "Failed to connect to the API server - it might still be starting up"; - } else if (error.message.includes("ENOTFOUND")) { - errorMsg = "Failed to resolve host - check the API server address"; - } else if (error.message) { - errorMsg = error.message; - } - - logger.error(`API request failed: ${errorMsg}`); - throw new Error(`API request failed: ${errorMsg}`); - } -} - -/** - * Run a VM using fetch. - * - * @param vmName - Name of the VM to run - * @param host - API host - * @param port - API port - * @param runOpts - Dictionary of run options - * @param storage - Storage path for the VM - * @param debug - Whether to show debug output - * @param verbose - Enable verbose logging - * @returns Dictionary with API response - */ -export async function lumeApiRun( - vmName: string, - host: string, - port: number, - runOpts: RunOptions, - storage?: string, - debug = false, - verbose = false -): Promise { - // URL encode the storage parameter for the query - let storageParam = ""; - - if (storage) { - const encodedStorage = encodeURIComponent(storage); - storageParam = `?storage=${encodedStorage}`; - } - - // Construct API URL - const apiUrl = `http://${host}:${port}/lume/vms/${vmName}/run${storageParam}`; - - // Convert run options to JSON - const jsonData = JSON.stringify(runOpts); - - if (debug || verbose) { - console.log(`Executing fetch API call: POST ${apiUrl}`); - console.log(`Request body: ${jsonData}`); - } - logger.info(`Executing API request: POST ${apiUrl}`); - - try { - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 20000); - - const response = await fetch(apiUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: jsonData, - signal: controller.signal, - }); - - clearTimeout(timeoutId); - - if (!response.ok) { - const errorMsg = `HTTP error returned from API server (status: ${response.status})`; - throw new Error(`API request failed: ${errorMsg}`); - } - - const data = (await response.json()) as VMInfo; - - if (debug || verbose) { - console.log(`DEBUG: API response: ${JSON.stringify(data, null, 2)}`); - } - - return data; - } catch (err) { - const error = err as Error; - let errorMsg = "Unknown error"; - - if (error.name === "AbortError") { - errorMsg = - "Operation timeout - the API server is taking too long to respond"; - } else if (error.message.includes("ECONNREFUSED")) { - errorMsg = - "Failed to connect to the API server - it might still be starting up"; - } else if (error.message.includes("ENOTFOUND")) { - errorMsg = "Failed to resolve host - check the API server address"; - } else if (error.message) { - errorMsg = error.message; - } - - logger.error(`API request failed: ${errorMsg}`); - throw new Error(`API request failed: ${errorMsg}`); - } -} - -/** - * Stop a VM using fetch. - * - * @param vmName - Name of the VM to stop - * @param host - API host - * @param port - API port - * @param storage - Storage path for the VM - * @param debug - Whether to show debug output - * @param verbose - Enable verbose logging - * @returns Dictionary with API response - */ -export async function lumeApiStop( - vmName: string, - host: string, - port: number, - storage?: string, - debug = false, - verbose = false -): Promise { - // URL encode the storage parameter for the query - let storageParam = ""; - - if (storage) { - const encodedStorage = encodeURIComponent(storage); - storageParam = `?storage=${encodedStorage}`; - } - - // Construct API URL - const apiUrl = `http://${host}:${port}/lume/vms/${vmName}/stop${storageParam}`; - - if (debug || verbose) { - console.log(`DEBUG: Executing fetch API call: POST ${apiUrl}`); - } - logger.debug(`Executing API request: POST ${apiUrl}`); - - try { - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 20000); - - const response = await fetch(apiUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: "{}", - signal: controller.signal, - }); - - clearTimeout(timeoutId); - - if (!response.ok) { - const errorMsg = `HTTP error returned from API server (status: ${response.status})`; - throw new Error(`API request failed: ${errorMsg}`); - } - - const data = (await response.json()) as VMInfo; - - if (debug || verbose) { - console.log(`DEBUG: API response: ${JSON.stringify(data, null, 2)}`); - } - - return data; - } catch (err) { - const error = err as Error; - let errorMsg = "Unknown error"; - - if (error.name === "AbortError") { - errorMsg = - "Operation timeout - the API server is taking too long to respond"; - } else if (error.message.includes("ECONNREFUSED")) { - errorMsg = - "Failed to connect to the API server - it might still be starting up"; - } else if (error.message.includes("ENOTFOUND")) { - errorMsg = "Failed to resolve host - check the API server address"; - } else if (error.message) { - errorMsg = error.message; - } - - logger.error(`API request failed: ${errorMsg}`); - throw new Error(`API request failed: ${errorMsg}`); - } -} - -/** - * Update VM settings using fetch. - * - * @param vmName - Name of the VM to update - * @param host - API host - * @param port - API port - * @param updateOpts - Dictionary of update options - * @param storage - Storage path for the VM - * @param debug - Whether to show debug output - * @param verbose - Enable verbose logging - * @returns Dictionary with API response - */ -export async function lumeApiUpdate( - vmName: string, - host: string, - port: number, - updateOpts: UpdateOptions, - storage?: string, - debug = false, - verbose = false -): Promise { - // URL encode the storage parameter for the query - let storageParam = ""; - - if (storage) { - const encodedStorage = encodeURIComponent(storage); - storageParam = `?storage=${encodedStorage}`; - } - - // Construct API URL - const apiUrl = `http://${host}:${port}/lume/vms/${vmName}/update${storageParam}`; - - // Convert update options to JSON - const jsonData = JSON.stringify(updateOpts); - - if (debug || verbose) { - console.log(`DEBUG: Executing fetch API call: POST ${apiUrl}`); - console.log(`DEBUG: Request body: ${jsonData}`); - } - logger.debug(`Executing API request: POST ${apiUrl}`); - - try { - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 20000); - - const response = await fetch(apiUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: jsonData, - signal: controller.signal, - }); - - clearTimeout(timeoutId); - - if (!response.ok) { - const errorMsg = `HTTP error returned from API server (status: ${response.status})`; - throw new Error(`API request failed: ${errorMsg}`); - } - - const data = (await response.json()) as VMInfo; - - if (debug || verbose) { - console.log(`DEBUG: API response: ${JSON.stringify(data, null, 2)}`); - } - - return data; - } catch (err) { - const error = err as Error; - let errorMsg = "Unknown error"; - - if (error.name === "AbortError") { - errorMsg = - "Operation timeout - the API server is taking too long to respond"; - } else if (error.message.includes("ECONNREFUSED")) { - errorMsg = - "Failed to connect to the API server - it might still be starting up"; - } else if (error.message.includes("ENOTFOUND")) { - errorMsg = "Failed to resolve host - check the API server address"; - } else if (error.message) { - errorMsg = error.message; - } - - logger.error(`API request failed: ${errorMsg}`); - throw new Error(`API request failed: ${errorMsg}`); - } -} - -/** - * Pull a VM image from a registry using fetch. - * - * @param image - Name/tag of the image to pull - * @param name - Name to give the VM after pulling - * @param host - API host - * @param port - API port - * @param storage - Storage path for the VM - * @param registry - Registry to pull from (default: ghcr.io) - * @param organization - Organization in registry (default: trycua) - * @param debug - Whether to show debug output - * @param verbose - Enable verbose logging - * @returns Dictionary with pull status and information - */ -export async function lumeApiPull( - image: string, - name: string, - host: string, - port: number, - storage?: string, - registry = "ghcr.io", - organization = "trycua", - debug = false, - verbose = false -): Promise { - // URL encode the storage parameter for the query - let storageParam = ""; - - if (storage) { - const encodedStorage = encodeURIComponent(storage); - storageParam = `?storage=${encodedStorage}`; - } - - // Construct API URL - const apiUrl = `http://${host}:${port}/lume/pull${storageParam}`; - - // Construct pull options - const pullOpts = { - image, - name, - registry, - organization, - }; - - const jsonData = JSON.stringify(pullOpts); - - if (debug || verbose) { - console.log(`DEBUG: Executing fetch API call: POST ${apiUrl}`); - console.log(`DEBUG: Request body: ${jsonData}`); - } - logger.debug(`Executing API request: POST ${apiUrl}`); - - try { - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 60000); // 60 second timeout for pulls - - const response = await fetch(apiUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: jsonData, - signal: controller.signal, - }); - - clearTimeout(timeoutId); - - if (!response.ok) { - const errorMsg = `HTTP error returned from API server (status: ${response.status})`; - throw new Error(`API request failed: ${errorMsg}`); - } - - const data = (await response.json()) as VMInfo; - - if (debug || verbose) { - console.log(`DEBUG: API response: ${JSON.stringify(data, null, 2)}`); - } - - return data; - } catch (err) { - const error = err as Error; - let errorMsg = "Unknown error"; - - if (error.name === "AbortError") { - errorMsg = - "Operation timeout - the API server is taking too long to respond"; - } else if (error.message.includes("ECONNREFUSED")) { - errorMsg = - "Failed to connect to the API server - it might still be starting up"; - } else if (error.message.includes("ENOTFOUND")) { - errorMsg = "Failed to resolve host - check the API server address"; - } else if (error.message) { - errorMsg = error.message; - } - - logger.error(`API request failed: ${errorMsg}`); - throw new Error(`API request failed: ${errorMsg}`); - } -} - -/** - * Delete a VM using fetch. - * - * @param vmName - Name of the VM to delete - * @param host - API host - * @param port - API port - * @param storage - Storage path for the VM - * @param debug - Whether to show debug output - * @param verbose - Enable verbose logging - * @returns Dictionary with API response - */ -export async function lumeApiDelete( - vmName: string, - host: string, - port: number, - storage?: string, - debug = false, - verbose = false -): Promise { - // URL encode the storage parameter for the query - let storageParam = ""; - - if (storage) { - const encodedStorage = encodeURIComponent(storage); - storageParam = `?storage=${encodedStorage}`; - } - - // Construct API URL - const apiUrl = `http://${host}:${port}/lume/vms/${vmName}${storageParam}`; - - if (debug || verbose) { - console.log(`DEBUG: Executing fetch API call: DELETE ${apiUrl}`); - } - logger.debug(`Executing API request: DELETE ${apiUrl}`); - - try { - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), 20000); - - const response = await fetch(apiUrl, { - method: "DELETE", - signal: controller.signal, - }); - - clearTimeout(timeoutId); - - if (!response.ok && response.status !== 404) { - const errorMsg = `HTTP error returned from API server (status: ${response.status})`; - throw new Error(`API request failed: ${errorMsg}`); - } - - // For 404, return null (VM already deleted) - if (response.status === 404) { - if (debug || verbose) { - console.log("DEBUG: VM not found (404) - treating as already deleted"); - } - return null; - } - - // Try to parse JSON response, but handle empty responses - let data: VMInfo | null = null; - const contentType = response.headers.get("content-type"); - if (contentType?.includes("application/json")) { - try { - data = (await response.json()) as VMInfo; - } catch { - // Empty response is OK for DELETE - } - } else { - // No JSON response expected - } - - if (debug || verbose) { - console.log(`DEBUG: API response: ${JSON.stringify(data, null, 2)}`); - } - - return data; - } catch (err) { - const error = err as Error; - let errorMsg = "Unknown error"; - - if (error.name === "AbortError") { - errorMsg = - "Operation timeout - the API server is taking too long to respond"; - } else if (error.message.includes("ECONNREFUSED")) { - errorMsg = - "Failed to connect to the API server - it might still be starting up"; - } else if (error.message.includes("ENOTFOUND")) { - errorMsg = "Failed to resolve host - check the API server address"; - } else if (error.message) { - errorMsg = error.message; - } - - logger.error(`API request failed: ${errorMsg}`); - throw new Error(`API request failed: ${errorMsg}`); - } -} diff --git a/libs/computer/typescript/tests/computer/cloud.test.ts b/libs/computer/typescript/tests/computer/cloud.test.ts index 78653c09..f1f7a1b6 100644 --- a/libs/computer/typescript/tests/computer/cloud.test.ts +++ b/libs/computer/typescript/tests/computer/cloud.test.ts @@ -1,14 +1,14 @@ import { describe, expect, it } from "vitest"; import { OSType } from "../../src/types"; -import { CloudComputer } from "../../src/computer/providers/cloud"; +import { Computer } from "../../src"; describe("Computer Cloud", () => { it("Should create computer instance", () => { - const cloud = new CloudComputer({ + const cloud = new Computer({ apiKey: "asdf", name: "s-linux-1234", osType: OSType.LINUX, }); - expect(cloud).toBeInstanceOf(CloudComputer); + expect(cloud).toBeInstanceOf(Computer); }); }); From b44442372e187084ca7282338817fa0426a83a13 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Mon, 23 Jun 2025 12:59:22 -0400 Subject: [PATCH 095/141] Moved dataclass to different file --- libs/computer/computer/interface/base.py | 3 +-- libs/computer/computer/interface/linux.py | 2 +- libs/computer/computer/interface/macos.py | 3 +-- libs/computer/computer/interface/models.py | 12 ++++++++++++ libs/computer/computer/models.py | 12 ------------ 5 files changed, 15 insertions(+), 17 deletions(-) diff --git a/libs/computer/computer/interface/base.py b/libs/computer/computer/interface/base.py index ec87d50a..a069e8c8 100644 --- a/libs/computer/computer/interface/base.py +++ b/libs/computer/computer/interface/base.py @@ -3,8 +3,7 @@ from abc import ABC, abstractmethod from typing import Optional, Dict, Any, Tuple, List from ..logger import Logger, LogLevel -from ..models import Computer, CommandResult, MouseButton - +from .models import MouseButton, CommandResult class BaseComputerInterface(ABC): """Base class for computer control interfaces.""" diff --git a/libs/computer/computer/interface/linux.py b/libs/computer/computer/interface/linux.py index d8f4a6de..b87118a2 100644 --- a/libs/computer/computer/interface/linux.py +++ b/libs/computer/computer/interface/linux.py @@ -9,7 +9,7 @@ import websockets from ..logger import Logger, LogLevel from .base import BaseComputerInterface from ..utils import decode_base64_image, encode_base64_image, bytes_to_image, draw_box, resize_image -from ..models import Computer, CommandResult, Key, KeyType, MouseButton +from .models import Key, KeyType, MouseButton, CommandResult class LinuxComputerInterface(BaseComputerInterface): """Interface for Linux.""" diff --git a/libs/computer/computer/interface/macos.py b/libs/computer/computer/interface/macos.py index da438ebb..cd31e74e 100644 --- a/libs/computer/computer/interface/macos.py +++ b/libs/computer/computer/interface/macos.py @@ -9,8 +9,7 @@ import websockets from ..logger import Logger, LogLevel from .base import BaseComputerInterface from ..utils import decode_base64_image, encode_base64_image, bytes_to_image, draw_box, resize_image -from ..models import Computer, CommandResult, Key, KeyType, MouseButton - +from .models import Key, KeyType, MouseButton, CommandResult class MacOSComputerInterface(BaseComputerInterface): """Interface for macOS.""" diff --git a/libs/computer/computer/interface/models.py b/libs/computer/computer/interface/models.py index 515b5f2b..223ac321 100644 --- a/libs/computer/computer/interface/models.py +++ b/libs/computer/computer/interface/models.py @@ -1,5 +1,17 @@ from enum import Enum from typing import Dict, List, Any, TypedDict, Union, Literal +from dataclasses import dataclass + +@dataclass +class CommandResult: + stdout: str + stderr: str + returncode: int + + def __init__(self, stdout: str, stderr: str, returncode: int): + self.stdout = stdout + self.stderr = stderr + self.returncode = returncode # Navigation key literals NavigationKey = Literal['pagedown', 'pageup', 'home', 'end', 'left', 'right', 'up', 'down'] diff --git a/libs/computer/computer/models.py b/libs/computer/computer/models.py index 931c21eb..5ead143f 100644 --- a/libs/computer/computer/models.py +++ b/libs/computer/computer/models.py @@ -6,18 +6,6 @@ from typing import Optional, Any, Dict # Import base provider interface from .providers.base import BaseVMProvider -@dataclass -class CommandResult: - """Command result.""" - stdout: str - stderr: str - returncode: int - - def __init__(self, stdout: str, stderr: str, returncode: int): - self.stdout = stdout - self.stderr = stderr - self.returncode = returncode - @dataclass class Display: """Display configuration.""" From f00d94e81ddd83df15f356bd018bb9c5c1f6d6a1 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Mon, 23 Jun 2025 13:07:18 -0400 Subject: [PATCH 096/141] Fixed tests --- libs/computer/computer/interface/windows.py | 3 +-- tests/files.py | 18 +++++++++--------- tests/venv.py | 21 ++++++++++----------- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/libs/computer/computer/interface/windows.py b/libs/computer/computer/interface/windows.py index 62038a91..7f471e7a 100644 --- a/libs/computer/computer/interface/windows.py +++ b/libs/computer/computer/interface/windows.py @@ -9,8 +9,7 @@ import websockets from ..logger import Logger, LogLevel from .base import BaseComputerInterface from ..utils import decode_base64_image, encode_base64_image, bytes_to_image, draw_box, resize_image -from ..models import Computer, CommandResult, Key, KeyType, MouseButton - +from .models import Key, KeyType, MouseButton, CommandResult class WindowsComputerInterface(BaseComputerInterface): """Interface for Windows.""" diff --git a/tests/files.py b/tests/files.py index 388b7656..236ef9e2 100644 --- a/tests/files.py +++ b/tests/files.py @@ -28,24 +28,24 @@ for path in pythonpath.split(":"): sys.path.insert(0, path) # Insert at beginning to prioritize print(f"Added to sys.path: {path}") -from computer.computer import Computer +from computer import Computer, VMProviderType @pytest.fixture(scope="session") async def computer(): """Shared Computer instance for all test cases.""" - # # Create a remote Linux computer with C/ua - # computer = Computer( - # os_type="linux", - # api_key=os.getenv("CUA_API_KEY"), - # name=str(os.getenv("CUA_CONTAINER_NAME")), - # provider_type=VMProviderType.CLOUD, - # ) + # Create a remote Linux computer with C/ua + computer = Computer( + os_type="linux", + api_key=os.getenv("CUA_API_KEY"), + name=str(os.getenv("CUA_CONTAINER_NAME")), + provider_type=VMProviderType.CLOUD, + ) # Create a local macOS computer with C/ua # computer = Computer() # Connect to host computer - computer = Computer(use_host_computer_server=True) + # computer = Computer(use_host_computer_server=True) try: await computer.run() diff --git a/tests/venv.py b/tests/venv.py index 7097c2fd..522a4727 100644 --- a/tests/venv.py +++ b/tests/venv.py @@ -29,24 +29,23 @@ for path in pythonpath.split(":"): sys.path.insert(0, path) # Insert at beginning to prioritize print(f"Added to sys.path: {path}") -from computer.computer import Computer -from computer.providers.base import VMProviderType +from computer import Computer, VMProviderType from computer.helpers import sandboxed, set_default_computer @pytest.fixture(scope="session") async def computer(): """Shared Computer instance for all test cases.""" - # # Create a remote Linux computer with C/ua - # computer = Computer( - # os_type="linux", - # api_key=os.getenv("CUA_API_KEY"), - # name=str(os.getenv("CUA_CONTAINER_NAME")), - # provider_type=VMProviderType.CLOUD, - # ) + # Create a remote Linux computer with C/ua + computer = Computer( + os_type="linux", + api_key=os.getenv("CUA_API_KEY"), + name=str(os.getenv("CUA_CONTAINER_NAME")), + provider_type=VMProviderType.CLOUD, + ) - # Create a local macOS computer with C/ua - computer = Computer() + # # Create a local macOS computer with C/ua + # computer = Computer() try: await computer.run() From 0246d1834735d9752667213667af9222dd2fb1ee Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Mon, 23 Jun 2025 10:22:36 -0700 Subject: [PATCH 097/141] Reorganize lib folder w/typescript and python roots, initialize core library. --- .vscode/computer-ts.code-workspace | 7 +- .vscode/core-ts.code-workspace | 13 + libs/lume/scripts/build/build-debug.sh | 4 - .../scripts/build/build-release-notarized.sh | 211 - libs/lume/scripts/build/build-release.sh | 21 - libs/{ => python}/agent/README.md | 0 libs/{ => python}/agent/agent/__init__.py | 0 .../{ => python}/agent/agent/core/__init__.py | 0 libs/{ => python}/agent/agent/core/agent.py | 0 libs/{ => python}/agent/agent/core/base.py | 0 .../agent/agent/core/callbacks.py | 0 .../agent/agent/core/experiment.py | 0 libs/{ => python}/agent/agent/core/factory.py | 0 .../{ => python}/agent/agent/core/messages.py | 0 .../agent/agent/core/provider_config.py | 0 .../agent/agent/core/telemetry.py | 0 libs/{ => python}/agent/agent/core/tools.py | 0 .../agent/agent/core/tools/__init__.py | 0 .../agent/agent/core/tools/base.py | 0 .../agent/agent/core/tools/bash.py | 0 .../agent/agent/core/tools/collection.py | 0 .../agent/agent/core/tools/computer.py | 0 .../agent/agent/core/tools/edit.py | 0 .../agent/agent/core/tools/manager.py | 0 libs/{ => python}/agent/agent/core/types.py | 0 .../agent/agent/core/visualization.py | 0 .../agent/agent/providers/__init__.py | 0 .../agent/providers/anthropic/__init__.py | 0 .../agent/providers/anthropic/api/client.py | 0 .../agent/providers/anthropic/api/logging.py | 0 .../agent/providers/anthropic/api_handler.py | 0 .../providers/anthropic/callbacks/__init__.py | 0 .../providers/anthropic/callbacks/manager.py | 0 .../agent/agent/providers/anthropic/loop.py | 0 .../agent/providers/anthropic/prompts.py | 0 .../providers/anthropic/response_handler.py | 0 .../providers/anthropic/tools/__init__.py | 0 .../agent/providers/anthropic/tools/base.py | 0 .../agent/providers/anthropic/tools/bash.py | 0 .../providers/anthropic/tools/collection.py | 0 .../providers/anthropic/tools/computer.py | 0 .../agent/providers/anthropic/tools/edit.py | 0 .../providers/anthropic/tools/manager.py | 0 .../agent/providers/anthropic/tools/run.py | 0 .../agent/agent/providers/anthropic/types.py | 0 .../agent/agent/providers/anthropic/utils.py | 0 .../agent/agent/providers/omni/__init__.py | 0 .../agent/agent/providers/omni/api_handler.py | 0 .../agent/providers/omni/clients/anthropic.py | 0 .../agent/providers/omni/clients/base.py | 0 .../agent/providers/omni/clients/oaicompat.py | 0 .../agent/providers/omni/clients/ollama.py | 0 .../agent/providers/omni/clients/openai.py | 0 .../agent/providers/omni/clients/utils.py | 0 .../agent/agent/providers/omni/image_utils.py | 0 .../agent/agent/providers/omni/loop.py | 0 .../agent/agent/providers/omni/parser.py | 0 .../agent/agent/providers/omni/prompts.py | 0 .../agent/providers/omni/tools/__init__.py | 0 .../agent/agent/providers/omni/tools/base.py | 0 .../agent/agent/providers/omni/tools/bash.py | 0 .../agent/providers/omni/tools/computer.py | 0 .../agent/providers/omni/tools/manager.py | 0 .../agent/agent/providers/omni/utils.py | 0 .../agent/agent/providers/openai/__init__.py | 0 .../agent/providers/openai/api_handler.py | 0 .../agent/agent/providers/openai/loop.py | 0 .../providers/openai/response_handler.py | 0 .../agent/providers/openai/tools/__init__.py | 0 .../agent/providers/openai/tools/base.py | 0 .../agent/providers/openai/tools/computer.py | 0 .../agent/providers/openai/tools/manager.py | 0 .../agent/agent/providers/openai/types.py | 0 .../agent/agent/providers/openai/utils.py | 0 .../agent/agent/providers/uitars/__init__.py | 0 .../agent/providers/uitars/clients/base.py | 0 .../agent/providers/uitars/clients/mlxvlm.py | 0 .../providers/uitars/clients/oaicompat.py | 0 .../agent/agent/providers/uitars/loop.py | 0 .../agent/agent/providers/uitars/prompts.py | 0 .../agent/providers/uitars/tools/__init__.py | 0 .../agent/providers/uitars/tools/computer.py | 0 .../agent/providers/uitars/tools/manager.py | 0 .../agent/agent/providers/uitars/utils.py | 0 libs/{ => python}/agent/agent/telemetry.py | 0 libs/{ => python}/agent/agent/ui/__init__.py | 0 libs/{ => python}/agent/agent/ui/__main__.py | 0 .../agent/agent/ui/gradio/__init__.py | 0 .../{ => python}/agent/agent/ui/gradio/app.py | 0 libs/{ => python}/agent/poetry.toml | 0 libs/{ => python}/agent/pyproject.toml | 0 libs/{ => python}/computer-server/README.md | 0 .../computer_server/__init__.py | 0 .../computer_server/__main__.py | 0 .../computer-server/computer_server/cli.py | 0 .../computer_server/diorama/__init__.py | 0 .../computer_server/diorama/base.py | 0 .../computer_server/diorama/diorama.py | 0 .../diorama/diorama_computer.py | 0 .../computer_server/diorama/draw.py | 0 .../computer_server/diorama/macos.py | 0 .../computer_server/diorama/safezone.py | 0 .../computer_server/handlers/base.py | 0 .../computer_server/handlers/factory.py | 0 .../computer_server/handlers/generic.py | 0 .../computer_server/handlers/linux.py | 0 .../computer_server/handlers/macos.py | 0 .../computer_server/handlers/windows.py | 0 .../computer-server/computer_server/main.py | 0 .../computer-server/computer_server/server.py | 0 .../computer-server/examples/__init__.py | 0 .../computer-server/examples/usage_example.py | 0 .../computer-server/pyproject.toml | 0 .../computer-server/run_server.py | 0 .../computer-server/test_connection.py | 0 .../python => python/computer}/README.md | 0 .../computer}/computer/__init__.py | 0 .../computer}/computer/computer.py | 0 .../computer}/computer/diorama_computer.py | 0 .../computer}/computer/helpers.py | 0 .../computer}/computer/interface/__init__.py | 0 .../computer}/computer/interface/base.py | 0 .../computer}/computer/interface/factory.py | 0 .../computer}/computer/interface/linux.py | 0 .../computer}/computer/interface/macos.py | 0 .../computer}/computer/interface/models.py | 0 .../computer}/computer/interface/windows.py | 0 .../computer}/computer/logger.py | 0 .../computer}/computer/models.py | 0 .../computer}/computer/providers/__init__.py | 0 .../computer}/computer/providers/base.py | 0 .../computer/providers/cloud/__init__.py | 0 .../computer/providers/cloud/provider.py | 0 .../computer}/computer/providers/factory.py | 0 .../computer/providers/lume/__init__.py | 0 .../computer/providers/lume/provider.py | 0 .../computer}/computer/providers/lume_api.py | 0 .../computer/providers/lumier/__init__.py | 0 .../computer/providers/lumier/provider.py | 0 .../computer/providers/winsandbox/__init__.py | 0 .../computer/providers/winsandbox/provider.py | 0 .../providers/winsandbox/setup_script.ps1 | 0 .../computer}/computer/telemetry.py | 0 .../computer}/computer/ui/__init__.py | 0 .../computer}/computer/ui/__main__.py | 0 .../computer}/computer/ui/gradio/__init__.py | 0 .../computer}/computer/ui/gradio/app.py | 0 .../computer}/computer/utils.py | 0 .../python => python/computer}/poetry.toml | 0 .../python => python/computer}/pyproject.toml | 0 libs/{ => python}/core/README.md | 0 libs/{ => python}/core/core/__init__.py | 0 .../core/core/telemetry/__init__.py | 0 .../core/core/telemetry/client.py | 0 .../core/core/telemetry/models.py | 0 .../core/core/telemetry/posthog_client.py | 0 .../core/core/telemetry/sender.py | 0 .../core/core/telemetry/telemetry.py | 0 libs/{ => python}/core/poetry.toml | 0 libs/{ => python}/core/pyproject.toml | 0 libs/{ => python}/lume/.cursorignore | 0 libs/{ => python}/lume/CONTRIBUTING.md | 0 libs/{ => python}/lume/Package.resolved | 0 libs/{ => python}/lume/Package.swift | 0 libs/{ => python}/lume/README.md | 0 libs/{ => python}/lume/docs/API-Reference.md | 0 libs/{ => python}/lume/docs/Development.md | 0 libs/{ => python}/lume/docs/FAQ.md | 0 libs/{ => python}/lume/img/cli.png | Bin libs/{ => python}/lume/img/logo_black.png | Bin libs/{ => python}/lume/img/logo_white.png | Bin .../lume/resources/lume.entitlements | 0 libs/{ => python}/lume/scripts/install.sh | 0 .../lume/src/Commands/Clone.swift | 0 .../lume/src/Commands/Config.swift | 0 .../lume/src/Commands/Create.swift | 0 .../lume/src/Commands/Delete.swift | 0 libs/{ => python}/lume/src/Commands/Get.swift | 0 .../{ => python}/lume/src/Commands/IPSW.swift | 0 .../lume/src/Commands/Images.swift | 0 .../{ => python}/lume/src/Commands/List.swift | 0 .../{ => python}/lume/src/Commands/Logs.swift | 0 .../src/Commands/Options/FormatOption.swift | 0 .../lume/src/Commands/Prune.swift | 0 .../{ => python}/lume/src/Commands/Pull.swift | 0 .../{ => python}/lume/src/Commands/Push.swift | 0 libs/{ => python}/lume/src/Commands/Run.swift | 0 .../lume/src/Commands/Serve.swift | 0 libs/{ => python}/lume/src/Commands/Set.swift | 0 .../{ => python}/lume/src/Commands/Stop.swift | 0 .../ImageContainerRegistry.swift | 0 .../src/ContainerRegistry/ImageList.swift | 0 .../src/ContainerRegistry/ImagesPrinter.swift | 0 .../{ => python}/lume/src/Errors/Errors.swift | 0 .../lume/src/FileSystem/Home.swift | 0 .../lume/src/FileSystem/Settings.swift | 0 .../lume/src/FileSystem/VMConfig.swift | 0 .../lume/src/FileSystem/VMDirectory.swift | 0 .../lume/src/FileSystem/VMLocation.swift | 0 .../lume/src/LumeController.swift | 0 libs/{ => python}/lume/src/Main.swift | 0 libs/{ => python}/lume/src/Server/HTTP.swift | 0 .../lume/src/Server/Handlers.swift | 0 .../lume/src/Server/Requests.swift | 0 .../lume/src/Server/Responses.swift | 0 .../{ => python}/lume/src/Server/Server.swift | 0 .../lume/src/Utils/CommandRegistry.swift | 0 .../lume/src/Utils/CommandUtils.swift | 0 libs/{ => python}/lume/src/Utils/Logger.swift | 0 .../lume/src/Utils/NetworkUtils.swift | 0 libs/{ => python}/lume/src/Utils/Path.swift | 0 .../lume/src/Utils/ProcessRunner.swift | 0 .../lume/src/Utils/ProgressLogger.swift | 0 libs/{ => python}/lume/src/Utils/String.swift | 0 libs/{ => python}/lume/src/Utils/Utils.swift | 0 libs/{ => python}/lume/src/VM/DarwinVM.swift | 0 libs/{ => python}/lume/src/VM/LinuxVM.swift | 0 libs/{ => python}/lume/src/VM/VM.swift | 0 libs/{ => python}/lume/src/VM/VMDetails.swift | 0 .../lume/src/VM/VMDetailsPrinter.swift | 0 .../lume/src/VM/VMDisplayResolution.swift | 0 libs/{ => python}/lume/src/VM/VMFactory.swift | 0 .../lume/src/VNC/PassphraseGenerator.swift | 0 .../lume/src/VNC/VNCService.swift | 0 .../src/Virtualization/DHCPLeaseParser.swift | 0 .../Virtualization/DarwinImageLoader.swift | 0 .../Virtualization/ImageLoaderFactory.swift | 0 .../VMVirtualizationService.swift | 0 .../lume/tests/Mocks/MockVM.swift | 0 .../Mocks/MockVMVirtualizationService.swift | 0 .../lume/tests/Mocks/MockVNCService.swift | 0 .../lume/tests/VM/VMDetailsPrinterTests.swift | 0 libs/{ => python}/lume/tests/VMTests.swift | 0 .../tests/VMVirtualizationServiceTests.swift | 0 .../lume/tests/VNCServiceTests.swift | 0 libs/{ => python}/lumier/.dockerignore | 0 libs/{ => python}/lumier/Dockerfile | 0 libs/{ => python}/lumier/README.md | 0 libs/{ => python}/lumier/src/bin/entry.sh | 0 .../lumier/src/config/constants.sh | 0 .../{ => python}/lumier/src/hooks/on-logon.sh | 0 libs/{ => python}/lumier/src/lib/utils.sh | 0 libs/{ => python}/lumier/src/lib/vm.sh | 0 libs/{ => python}/mcp-server/README.md | 0 .../mcp-server/mcp_server/__init__.py | 0 .../mcp-server/mcp_server/__main__.py | 0 .../mcp-server/mcp_server/server.py | 0 libs/{ => python}/mcp-server/pyproject.toml | 0 .../mcp-server/scripts/install_mcp_server.sh | 0 .../mcp-server/scripts/start_mcp_server.sh | 0 libs/{ => python}/pylume/README.md | 0 libs/{ => python}/pylume/__init__.py | 0 libs/{ => python}/pylume/pylume/__init__.py | 0 libs/{ => python}/pylume/pylume/client.py | 0 libs/{ => python}/pylume/pylume/exceptions.py | 0 libs/{ => python}/pylume/pylume/lume | Bin libs/{ => python}/pylume/pylume/models.py | 0 libs/{ => python}/pylume/pylume/pylume.py | 0 libs/{ => python}/pylume/pylume/server.py | 0 libs/{ => python}/pylume/pyproject.toml | 0 libs/{ => python}/som/README.md | 0 libs/{ => python}/som/poetry.toml | 0 libs/{ => python}/som/pyproject.toml | 0 libs/{ => python}/som/som/__init__.py | 0 libs/{ => python}/som/som/detect.py | 0 libs/{ => python}/som/som/detection.py | 0 libs/{ => python}/som/som/models.py | 0 libs/{ => python}/som/som/ocr.py | 0 libs/{ => python}/som/som/util/utils.py | 0 libs/{ => python}/som/som/visualization.py | 0 .../{ => python}/som/tests/test_omniparser.py | 0 .../computer}/.editorconfig | 0 .../computer}/.gitattributes | 0 .../computer}/.github/_workflows/release.yml | 0 .../.github/_workflows/unit-test.yml | 0 .../computer}/.gitignore | 0 .../typescript => typescript/computer}/.nvmrc | 0 .../computer}/LICENSE | 0 .../computer}/README.md | 0 .../computer}/biome.json | 0 .../computer}/package.json | 0 .../computer}/pnpm-lock.yaml | 0 .../computer}/pnpm-workspace.yaml | 0 .../computer}/src/computer/index.ts | 0 .../computer}/src/computer/providers/base.ts | 0 .../computer}/src/computer/providers/cloud.ts | 0 .../computer}/src/computer/providers/index.ts | 0 .../computer}/src/computer/types.ts | 0 .../computer}/src/index.ts | 0 .../computer}/src/interface/base.ts | 0 .../computer}/src/interface/factory.ts | 0 .../computer}/src/interface/index.ts | 0 .../computer}/src/interface/linux.ts | 0 .../computer}/src/interface/macos.ts | 0 .../computer}/src/interface/windows.ts | 0 .../computer}/src/types.ts | 0 .../computer}/tests/computer/cloud.test.ts | 0 .../computer}/tests/interface/factory.test.ts | 0 .../computer}/tests/interface/index.test.ts | 0 .../computer}/tests/interface/linux.test.ts | 0 .../computer}/tests/interface/macos.test.ts | 0 .../computer}/tests/interface/windows.test.ts | 0 .../computer}/tests/setup.ts | 0 .../computer}/tsconfig.json | 0 .../computer}/tsdown.config.ts | 0 .../computer}/vitest.config.ts | 0 libs/typescript/core/.editorconfig | 6 + libs/typescript/core/.gitattributes | 1 + .../core/.github/_workflows/release.yml | 26 + .../core/.github/_workflows/unit-test.yml | 38 + libs/typescript/core/.gitignore | 6 + libs/typescript/core/.vscode/settings.json | 3 + libs/typescript/core/LICENSE | 21 + libs/typescript/core/README.md | 27 + libs/typescript/core/biome.json | 86 + libs/typescript/core/package.json | 54 + libs/typescript/core/pnpm-lock.yaml | 5724 +++++++++++++++++ libs/typescript/core/src/index.ts | 3 + libs/typescript/core/tests/index.test.ts | 6 + libs/typescript/core/tsconfig.json | 20 + libs/typescript/core/tsdown.config.ts | 9 + libs/typescript/core/vitest.config.ts | 3 + 322 files changed, 6052 insertions(+), 237 deletions(-) create mode 100644 .vscode/core-ts.code-workspace delete mode 100755 libs/lume/scripts/build/build-debug.sh delete mode 100755 libs/lume/scripts/build/build-release-notarized.sh delete mode 100755 libs/lume/scripts/build/build-release.sh rename libs/{ => python}/agent/README.md (100%) rename libs/{ => python}/agent/agent/__init__.py (100%) rename libs/{ => python}/agent/agent/core/__init__.py (100%) rename libs/{ => python}/agent/agent/core/agent.py (100%) rename libs/{ => python}/agent/agent/core/base.py (100%) rename libs/{ => python}/agent/agent/core/callbacks.py (100%) rename libs/{ => python}/agent/agent/core/experiment.py (100%) rename libs/{ => python}/agent/agent/core/factory.py (100%) rename libs/{ => python}/agent/agent/core/messages.py (100%) rename libs/{ => python}/agent/agent/core/provider_config.py (100%) rename libs/{ => python}/agent/agent/core/telemetry.py (100%) rename libs/{ => python}/agent/agent/core/tools.py (100%) rename libs/{ => python}/agent/agent/core/tools/__init__.py (100%) rename libs/{ => python}/agent/agent/core/tools/base.py (100%) rename libs/{ => python}/agent/agent/core/tools/bash.py (100%) rename libs/{ => python}/agent/agent/core/tools/collection.py (100%) rename libs/{ => python}/agent/agent/core/tools/computer.py (100%) rename libs/{ => python}/agent/agent/core/tools/edit.py (100%) rename libs/{ => python}/agent/agent/core/tools/manager.py (100%) rename libs/{ => python}/agent/agent/core/types.py (100%) rename libs/{ => python}/agent/agent/core/visualization.py (100%) rename libs/{ => python}/agent/agent/providers/__init__.py (100%) rename libs/{ => python}/agent/agent/providers/anthropic/__init__.py (100%) rename libs/{ => python}/agent/agent/providers/anthropic/api/client.py (100%) rename libs/{ => python}/agent/agent/providers/anthropic/api/logging.py (100%) rename libs/{ => python}/agent/agent/providers/anthropic/api_handler.py (100%) rename libs/{ => python}/agent/agent/providers/anthropic/callbacks/__init__.py (100%) rename libs/{ => python}/agent/agent/providers/anthropic/callbacks/manager.py (100%) rename libs/{ => python}/agent/agent/providers/anthropic/loop.py (100%) rename libs/{ => python}/agent/agent/providers/anthropic/prompts.py (100%) rename libs/{ => python}/agent/agent/providers/anthropic/response_handler.py (100%) rename libs/{ => python}/agent/agent/providers/anthropic/tools/__init__.py (100%) rename libs/{ => python}/agent/agent/providers/anthropic/tools/base.py (100%) rename libs/{ => python}/agent/agent/providers/anthropic/tools/bash.py (100%) rename libs/{ => python}/agent/agent/providers/anthropic/tools/collection.py (100%) rename libs/{ => python}/agent/agent/providers/anthropic/tools/computer.py (100%) rename libs/{ => python}/agent/agent/providers/anthropic/tools/edit.py (100%) rename libs/{ => python}/agent/agent/providers/anthropic/tools/manager.py (100%) rename libs/{ => python}/agent/agent/providers/anthropic/tools/run.py (100%) rename libs/{ => python}/agent/agent/providers/anthropic/types.py (100%) rename libs/{ => python}/agent/agent/providers/anthropic/utils.py (100%) rename libs/{ => python}/agent/agent/providers/omni/__init__.py (100%) rename libs/{ => python}/agent/agent/providers/omni/api_handler.py (100%) rename libs/{ => python}/agent/agent/providers/omni/clients/anthropic.py (100%) rename libs/{ => python}/agent/agent/providers/omni/clients/base.py (100%) rename libs/{ => python}/agent/agent/providers/omni/clients/oaicompat.py (100%) rename libs/{ => python}/agent/agent/providers/omni/clients/ollama.py (100%) rename libs/{ => python}/agent/agent/providers/omni/clients/openai.py (100%) rename libs/{ => python}/agent/agent/providers/omni/clients/utils.py (100%) rename libs/{ => python}/agent/agent/providers/omni/image_utils.py (100%) rename libs/{ => python}/agent/agent/providers/omni/loop.py (100%) rename libs/{ => python}/agent/agent/providers/omni/parser.py (100%) rename libs/{ => python}/agent/agent/providers/omni/prompts.py (100%) rename libs/{ => python}/agent/agent/providers/omni/tools/__init__.py (100%) rename libs/{ => python}/agent/agent/providers/omni/tools/base.py (100%) rename libs/{ => python}/agent/agent/providers/omni/tools/bash.py (100%) rename libs/{ => python}/agent/agent/providers/omni/tools/computer.py (100%) rename libs/{ => python}/agent/agent/providers/omni/tools/manager.py (100%) rename libs/{ => python}/agent/agent/providers/omni/utils.py (100%) rename libs/{ => python}/agent/agent/providers/openai/__init__.py (100%) rename libs/{ => python}/agent/agent/providers/openai/api_handler.py (100%) rename libs/{ => python}/agent/agent/providers/openai/loop.py (100%) rename libs/{ => python}/agent/agent/providers/openai/response_handler.py (100%) rename libs/{ => python}/agent/agent/providers/openai/tools/__init__.py (100%) rename libs/{ => python}/agent/agent/providers/openai/tools/base.py (100%) rename libs/{ => python}/agent/agent/providers/openai/tools/computer.py (100%) rename libs/{ => python}/agent/agent/providers/openai/tools/manager.py (100%) rename libs/{ => python}/agent/agent/providers/openai/types.py (100%) rename libs/{ => python}/agent/agent/providers/openai/utils.py (100%) rename libs/{ => python}/agent/agent/providers/uitars/__init__.py (100%) rename libs/{ => python}/agent/agent/providers/uitars/clients/base.py (100%) rename libs/{ => python}/agent/agent/providers/uitars/clients/mlxvlm.py (100%) rename libs/{ => python}/agent/agent/providers/uitars/clients/oaicompat.py (100%) rename libs/{ => python}/agent/agent/providers/uitars/loop.py (100%) rename libs/{ => python}/agent/agent/providers/uitars/prompts.py (100%) rename libs/{ => python}/agent/agent/providers/uitars/tools/__init__.py (100%) rename libs/{ => python}/agent/agent/providers/uitars/tools/computer.py (100%) rename libs/{ => python}/agent/agent/providers/uitars/tools/manager.py (100%) rename libs/{ => python}/agent/agent/providers/uitars/utils.py (100%) rename libs/{ => python}/agent/agent/telemetry.py (100%) rename libs/{ => python}/agent/agent/ui/__init__.py (100%) rename libs/{ => python}/agent/agent/ui/__main__.py (100%) rename libs/{ => python}/agent/agent/ui/gradio/__init__.py (100%) rename libs/{ => python}/agent/agent/ui/gradio/app.py (100%) rename libs/{ => python}/agent/poetry.toml (100%) rename libs/{ => python}/agent/pyproject.toml (100%) rename libs/{ => python}/computer-server/README.md (100%) rename libs/{ => python}/computer-server/computer_server/__init__.py (100%) rename libs/{ => python}/computer-server/computer_server/__main__.py (100%) rename libs/{ => python}/computer-server/computer_server/cli.py (100%) rename libs/{ => python}/computer-server/computer_server/diorama/__init__.py (100%) rename libs/{ => python}/computer-server/computer_server/diorama/base.py (100%) rename libs/{ => python}/computer-server/computer_server/diorama/diorama.py (100%) rename libs/{ => python}/computer-server/computer_server/diorama/diorama_computer.py (100%) rename libs/{ => python}/computer-server/computer_server/diorama/draw.py (100%) rename libs/{ => python}/computer-server/computer_server/diorama/macos.py (100%) rename libs/{ => python}/computer-server/computer_server/diorama/safezone.py (100%) rename libs/{ => python}/computer-server/computer_server/handlers/base.py (100%) rename libs/{ => python}/computer-server/computer_server/handlers/factory.py (100%) rename libs/{ => python}/computer-server/computer_server/handlers/generic.py (100%) rename libs/{ => python}/computer-server/computer_server/handlers/linux.py (100%) rename libs/{ => python}/computer-server/computer_server/handlers/macos.py (100%) rename libs/{ => python}/computer-server/computer_server/handlers/windows.py (100%) rename libs/{ => python}/computer-server/computer_server/main.py (100%) rename libs/{ => python}/computer-server/computer_server/server.py (100%) rename libs/{ => python}/computer-server/examples/__init__.py (100%) rename libs/{ => python}/computer-server/examples/usage_example.py (100%) rename libs/{ => python}/computer-server/pyproject.toml (100%) rename libs/{ => python}/computer-server/run_server.py (100%) rename libs/{ => python}/computer-server/test_connection.py (100%) rename libs/{computer/python => python/computer}/README.md (100%) rename libs/{computer/python => python/computer}/computer/__init__.py (100%) rename libs/{computer/python => python/computer}/computer/computer.py (100%) rename libs/{computer/python => python/computer}/computer/diorama_computer.py (100%) rename libs/{computer/python => python/computer}/computer/helpers.py (100%) rename libs/{computer/python => python/computer}/computer/interface/__init__.py (100%) rename libs/{computer/python => python/computer}/computer/interface/base.py (100%) rename libs/{computer/python => python/computer}/computer/interface/factory.py (100%) rename libs/{computer/python => python/computer}/computer/interface/linux.py (100%) rename libs/{computer/python => python/computer}/computer/interface/macos.py (100%) rename libs/{computer/python => python/computer}/computer/interface/models.py (100%) rename libs/{computer/python => python/computer}/computer/interface/windows.py (100%) rename libs/{computer/python => python/computer}/computer/logger.py (100%) rename libs/{computer/python => python/computer}/computer/models.py (100%) rename libs/{computer/python => python/computer}/computer/providers/__init__.py (100%) rename libs/{computer/python => python/computer}/computer/providers/base.py (100%) rename libs/{computer/python => python/computer}/computer/providers/cloud/__init__.py (100%) rename libs/{computer/python => python/computer}/computer/providers/cloud/provider.py (100%) rename libs/{computer/python => python/computer}/computer/providers/factory.py (100%) rename libs/{computer/python => python/computer}/computer/providers/lume/__init__.py (100%) rename libs/{computer/python => python/computer}/computer/providers/lume/provider.py (100%) rename libs/{computer/python => python/computer}/computer/providers/lume_api.py (100%) rename libs/{computer/python => python/computer}/computer/providers/lumier/__init__.py (100%) rename libs/{computer/python => python/computer}/computer/providers/lumier/provider.py (100%) rename libs/{computer/python => python/computer}/computer/providers/winsandbox/__init__.py (100%) rename libs/{computer/python => python/computer}/computer/providers/winsandbox/provider.py (100%) rename libs/{computer/python => python/computer}/computer/providers/winsandbox/setup_script.ps1 (100%) rename libs/{computer/python => python/computer}/computer/telemetry.py (100%) rename libs/{computer/python => python/computer}/computer/ui/__init__.py (100%) rename libs/{computer/python => python/computer}/computer/ui/__main__.py (100%) rename libs/{computer/python => python/computer}/computer/ui/gradio/__init__.py (100%) rename libs/{computer/python => python/computer}/computer/ui/gradio/app.py (100%) rename libs/{computer/python => python/computer}/computer/utils.py (100%) rename libs/{computer/python => python/computer}/poetry.toml (100%) rename libs/{computer/python => python/computer}/pyproject.toml (100%) rename libs/{ => python}/core/README.md (100%) rename libs/{ => python}/core/core/__init__.py (100%) rename libs/{ => python}/core/core/telemetry/__init__.py (100%) rename libs/{ => python}/core/core/telemetry/client.py (100%) rename libs/{ => python}/core/core/telemetry/models.py (100%) rename libs/{ => python}/core/core/telemetry/posthog_client.py (100%) rename libs/{ => python}/core/core/telemetry/sender.py (100%) rename libs/{ => python}/core/core/telemetry/telemetry.py (100%) rename libs/{ => python}/core/poetry.toml (100%) rename libs/{ => python}/core/pyproject.toml (100%) rename libs/{ => python}/lume/.cursorignore (100%) rename libs/{ => python}/lume/CONTRIBUTING.md (100%) rename libs/{ => python}/lume/Package.resolved (100%) rename libs/{ => python}/lume/Package.swift (100%) rename libs/{ => python}/lume/README.md (100%) rename libs/{ => python}/lume/docs/API-Reference.md (100%) rename libs/{ => python}/lume/docs/Development.md (100%) rename libs/{ => python}/lume/docs/FAQ.md (100%) rename libs/{ => python}/lume/img/cli.png (100%) rename libs/{ => python}/lume/img/logo_black.png (100%) rename libs/{ => python}/lume/img/logo_white.png (100%) rename libs/{ => python}/lume/resources/lume.entitlements (100%) rename libs/{ => python}/lume/scripts/install.sh (100%) rename libs/{ => python}/lume/src/Commands/Clone.swift (100%) rename libs/{ => python}/lume/src/Commands/Config.swift (100%) rename libs/{ => python}/lume/src/Commands/Create.swift (100%) rename libs/{ => python}/lume/src/Commands/Delete.swift (100%) rename libs/{ => python}/lume/src/Commands/Get.swift (100%) rename libs/{ => python}/lume/src/Commands/IPSW.swift (100%) rename libs/{ => python}/lume/src/Commands/Images.swift (100%) rename libs/{ => python}/lume/src/Commands/List.swift (100%) rename libs/{ => python}/lume/src/Commands/Logs.swift (100%) rename libs/{ => python}/lume/src/Commands/Options/FormatOption.swift (100%) rename libs/{ => python}/lume/src/Commands/Prune.swift (100%) rename libs/{ => python}/lume/src/Commands/Pull.swift (100%) rename libs/{ => python}/lume/src/Commands/Push.swift (100%) rename libs/{ => python}/lume/src/Commands/Run.swift (100%) rename libs/{ => python}/lume/src/Commands/Serve.swift (100%) rename libs/{ => python}/lume/src/Commands/Set.swift (100%) rename libs/{ => python}/lume/src/Commands/Stop.swift (100%) rename libs/{ => python}/lume/src/ContainerRegistry/ImageContainerRegistry.swift (100%) rename libs/{ => python}/lume/src/ContainerRegistry/ImageList.swift (100%) rename libs/{ => python}/lume/src/ContainerRegistry/ImagesPrinter.swift (100%) rename libs/{ => python}/lume/src/Errors/Errors.swift (100%) rename libs/{ => python}/lume/src/FileSystem/Home.swift (100%) rename libs/{ => python}/lume/src/FileSystem/Settings.swift (100%) rename libs/{ => python}/lume/src/FileSystem/VMConfig.swift (100%) rename libs/{ => python}/lume/src/FileSystem/VMDirectory.swift (100%) rename libs/{ => python}/lume/src/FileSystem/VMLocation.swift (100%) rename libs/{ => python}/lume/src/LumeController.swift (100%) rename libs/{ => python}/lume/src/Main.swift (100%) rename libs/{ => python}/lume/src/Server/HTTP.swift (100%) rename libs/{ => python}/lume/src/Server/Handlers.swift (100%) rename libs/{ => python}/lume/src/Server/Requests.swift (100%) rename libs/{ => python}/lume/src/Server/Responses.swift (100%) rename libs/{ => python}/lume/src/Server/Server.swift (100%) rename libs/{ => python}/lume/src/Utils/CommandRegistry.swift (100%) rename libs/{ => python}/lume/src/Utils/CommandUtils.swift (100%) rename libs/{ => python}/lume/src/Utils/Logger.swift (100%) rename libs/{ => python}/lume/src/Utils/NetworkUtils.swift (100%) rename libs/{ => python}/lume/src/Utils/Path.swift (100%) rename libs/{ => python}/lume/src/Utils/ProcessRunner.swift (100%) rename libs/{ => python}/lume/src/Utils/ProgressLogger.swift (100%) rename libs/{ => python}/lume/src/Utils/String.swift (100%) rename libs/{ => python}/lume/src/Utils/Utils.swift (100%) rename libs/{ => python}/lume/src/VM/DarwinVM.swift (100%) rename libs/{ => python}/lume/src/VM/LinuxVM.swift (100%) rename libs/{ => python}/lume/src/VM/VM.swift (100%) rename libs/{ => python}/lume/src/VM/VMDetails.swift (100%) rename libs/{ => python}/lume/src/VM/VMDetailsPrinter.swift (100%) rename libs/{ => python}/lume/src/VM/VMDisplayResolution.swift (100%) rename libs/{ => python}/lume/src/VM/VMFactory.swift (100%) rename libs/{ => python}/lume/src/VNC/PassphraseGenerator.swift (100%) rename libs/{ => python}/lume/src/VNC/VNCService.swift (100%) rename libs/{ => python}/lume/src/Virtualization/DHCPLeaseParser.swift (100%) rename libs/{ => python}/lume/src/Virtualization/DarwinImageLoader.swift (100%) rename libs/{ => python}/lume/src/Virtualization/ImageLoaderFactory.swift (100%) rename libs/{ => python}/lume/src/Virtualization/VMVirtualizationService.swift (100%) rename libs/{ => python}/lume/tests/Mocks/MockVM.swift (100%) rename libs/{ => python}/lume/tests/Mocks/MockVMVirtualizationService.swift (100%) rename libs/{ => python}/lume/tests/Mocks/MockVNCService.swift (100%) rename libs/{ => python}/lume/tests/VM/VMDetailsPrinterTests.swift (100%) rename libs/{ => python}/lume/tests/VMTests.swift (100%) rename libs/{ => python}/lume/tests/VMVirtualizationServiceTests.swift (100%) rename libs/{ => python}/lume/tests/VNCServiceTests.swift (100%) rename libs/{ => python}/lumier/.dockerignore (100%) rename libs/{ => python}/lumier/Dockerfile (100%) rename libs/{ => python}/lumier/README.md (100%) rename libs/{ => python}/lumier/src/bin/entry.sh (100%) rename libs/{ => python}/lumier/src/config/constants.sh (100%) rename libs/{ => python}/lumier/src/hooks/on-logon.sh (100%) rename libs/{ => python}/lumier/src/lib/utils.sh (100%) rename libs/{ => python}/lumier/src/lib/vm.sh (100%) rename libs/{ => python}/mcp-server/README.md (100%) rename libs/{ => python}/mcp-server/mcp_server/__init__.py (100%) rename libs/{ => python}/mcp-server/mcp_server/__main__.py (100%) rename libs/{ => python}/mcp-server/mcp_server/server.py (100%) rename libs/{ => python}/mcp-server/pyproject.toml (100%) rename libs/{ => python}/mcp-server/scripts/install_mcp_server.sh (100%) rename libs/{ => python}/mcp-server/scripts/start_mcp_server.sh (100%) rename libs/{ => python}/pylume/README.md (100%) rename libs/{ => python}/pylume/__init__.py (100%) rename libs/{ => python}/pylume/pylume/__init__.py (100%) rename libs/{ => python}/pylume/pylume/client.py (100%) rename libs/{ => python}/pylume/pylume/exceptions.py (100%) rename libs/{ => python}/pylume/pylume/lume (100%) rename libs/{ => python}/pylume/pylume/models.py (100%) rename libs/{ => python}/pylume/pylume/pylume.py (100%) rename libs/{ => python}/pylume/pylume/server.py (100%) rename libs/{ => python}/pylume/pyproject.toml (100%) rename libs/{ => python}/som/README.md (100%) rename libs/{ => python}/som/poetry.toml (100%) rename libs/{ => python}/som/pyproject.toml (100%) rename libs/{ => python}/som/som/__init__.py (100%) rename libs/{ => python}/som/som/detect.py (100%) rename libs/{ => python}/som/som/detection.py (100%) rename libs/{ => python}/som/som/models.py (100%) rename libs/{ => python}/som/som/ocr.py (100%) rename libs/{ => python}/som/som/util/utils.py (100%) rename libs/{ => python}/som/som/visualization.py (100%) rename libs/{ => python}/som/tests/test_omniparser.py (100%) rename libs/{computer/typescript => typescript/computer}/.editorconfig (100%) rename libs/{computer/typescript => typescript/computer}/.gitattributes (100%) rename libs/{computer/typescript => typescript/computer}/.github/_workflows/release.yml (100%) rename libs/{computer/typescript => typescript/computer}/.github/_workflows/unit-test.yml (100%) rename libs/{computer/typescript => typescript/computer}/.gitignore (100%) rename libs/{computer/typescript => typescript/computer}/.nvmrc (100%) rename libs/{computer/typescript => typescript/computer}/LICENSE (100%) rename libs/{computer/typescript => typescript/computer}/README.md (100%) rename libs/{computer/typescript => typescript/computer}/biome.json (100%) rename libs/{computer/typescript => typescript/computer}/package.json (100%) rename libs/{computer/typescript => typescript/computer}/pnpm-lock.yaml (100%) rename libs/{computer/typescript => typescript/computer}/pnpm-workspace.yaml (100%) rename libs/{computer/typescript => typescript/computer}/src/computer/index.ts (100%) rename libs/{computer/typescript => typescript/computer}/src/computer/providers/base.ts (100%) rename libs/{computer/typescript => typescript/computer}/src/computer/providers/cloud.ts (100%) rename libs/{computer/typescript => typescript/computer}/src/computer/providers/index.ts (100%) rename libs/{computer/typescript => typescript/computer}/src/computer/types.ts (100%) rename libs/{computer/typescript => typescript/computer}/src/index.ts (100%) rename libs/{computer/typescript => typescript/computer}/src/interface/base.ts (100%) rename libs/{computer/typescript => typescript/computer}/src/interface/factory.ts (100%) rename libs/{computer/typescript => typescript/computer}/src/interface/index.ts (100%) rename libs/{computer/typescript => typescript/computer}/src/interface/linux.ts (100%) rename libs/{computer/typescript => typescript/computer}/src/interface/macos.ts (100%) rename libs/{computer/typescript => typescript/computer}/src/interface/windows.ts (100%) rename libs/{computer/typescript => typescript/computer}/src/types.ts (100%) rename libs/{computer/typescript => typescript/computer}/tests/computer/cloud.test.ts (100%) rename libs/{computer/typescript => typescript/computer}/tests/interface/factory.test.ts (100%) rename libs/{computer/typescript => typescript/computer}/tests/interface/index.test.ts (100%) rename libs/{computer/typescript => typescript/computer}/tests/interface/linux.test.ts (100%) rename libs/{computer/typescript => typescript/computer}/tests/interface/macos.test.ts (100%) rename libs/{computer/typescript => typescript/computer}/tests/interface/windows.test.ts (100%) rename libs/{computer/typescript => typescript/computer}/tests/setup.ts (100%) rename libs/{computer/typescript => typescript/computer}/tsconfig.json (100%) rename libs/{computer/typescript => typescript/computer}/tsdown.config.ts (100%) rename libs/{computer/typescript => typescript/computer}/vitest.config.ts (100%) create mode 100644 libs/typescript/core/.editorconfig create mode 100644 libs/typescript/core/.gitattributes create mode 100644 libs/typescript/core/.github/_workflows/release.yml create mode 100644 libs/typescript/core/.github/_workflows/unit-test.yml create mode 100644 libs/typescript/core/.gitignore create mode 100644 libs/typescript/core/.vscode/settings.json create mode 100644 libs/typescript/core/LICENSE create mode 100644 libs/typescript/core/README.md create mode 100644 libs/typescript/core/biome.json create mode 100644 libs/typescript/core/package.json create mode 100644 libs/typescript/core/pnpm-lock.yaml create mode 100644 libs/typescript/core/src/index.ts create mode 100644 libs/typescript/core/tests/index.test.ts create mode 100644 libs/typescript/core/tsconfig.json create mode 100644 libs/typescript/core/tsdown.config.ts create mode 100644 libs/typescript/core/vitest.config.ts diff --git a/.vscode/computer-ts.code-workspace b/.vscode/computer-ts.code-workspace index fa3d872a..1c74b456 100644 --- a/.vscode/computer-ts.code-workspace +++ b/.vscode/computer-ts.code-workspace @@ -2,7 +2,12 @@ "folders": [ { "name": "computer-ts", - "path": "../libs/computer/typescript" + "path": "../libs/typescript/computer" } ], + "extensions": { + "recommendations": [ + "biomejs.biome", + ] + } } \ No newline at end of file diff --git a/.vscode/core-ts.code-workspace b/.vscode/core-ts.code-workspace new file mode 100644 index 00000000..3da96d33 --- /dev/null +++ b/.vscode/core-ts.code-workspace @@ -0,0 +1,13 @@ +{ + "folders": [ + { + "name": "core-ts", + "path": "../libs/typescript/core" + } + ], + "extensions": { + "recommendations": [ + "biomejs.biome", + ] + } +} \ No newline at end of file diff --git a/libs/lume/scripts/build/build-debug.sh b/libs/lume/scripts/build/build-debug.sh deleted file mode 100755 index 452e20c3..00000000 --- a/libs/lume/scripts/build/build-debug.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -swift build --product lume -codesign --force --entitlement resources/lume.entitlements --sign - .build/debug/lume diff --git a/libs/lume/scripts/build/build-release-notarized.sh b/libs/lume/scripts/build/build-release-notarized.sh deleted file mode 100755 index 603446b7..00000000 --- a/libs/lume/scripts/build/build-release-notarized.sh +++ /dev/null @@ -1,211 +0,0 @@ -#!/bin/bash - -# Set default log level if not provided -LOG_LEVEL=${LOG_LEVEL:-"normal"} - -# Function to log based on level -log() { - local level=$1 - local message=$2 - - case "$LOG_LEVEL" in - "minimal") - # Only show essential or error messages - if [ "$level" = "essential" ] || [ "$level" = "error" ]; then - echo "$message" - fi - ;; - "none") - # Show nothing except errors - if [ "$level" = "error" ]; then - echo "$message" >&2 - fi - ;; - *) - # Normal logging - show everything - echo "$message" - ;; - esac -} - -# Check required environment variables -required_vars=( - "CERT_APPLICATION_NAME" - "CERT_INSTALLER_NAME" - "APPLE_ID" - "TEAM_ID" - "APP_SPECIFIC_PASSWORD" -) - -for var in "${required_vars[@]}"; do - if [ -z "${!var}" ]; then - log "error" "Error: $var is not set" - exit 1 - fi -done - -# Get VERSION from environment or use default -VERSION=${VERSION:-"0.1.0"} - -# Move to the project root directory -pushd ../../ > /dev/null - -# Ensure .release directory exists and is clean -mkdir -p .release -log "normal" "Ensuring .release directory exists and is accessible" - -# Build the release version -log "essential" "Building release version..." -swift build -c release --product lume > /dev/null - -# Sign the binary with hardened runtime entitlements -log "essential" "Signing binary with entitlements..." -codesign --force --options runtime \ - --entitlement ./resources/lume.entitlements \ - --sign "$CERT_APPLICATION_NAME" \ - .build/release/lume 2> /dev/null - -# Create a temporary directory for packaging -TEMP_ROOT=$(mktemp -d) -mkdir -p "$TEMP_ROOT/usr/local/bin" -cp -f .build/release/lume "$TEMP_ROOT/usr/local/bin/" - -# Build the installer package -log "essential" "Building installer package..." -if ! pkgbuild --root "$TEMP_ROOT" \ - --identifier "com.trycua.lume" \ - --version "1.0" \ - --install-location "/" \ - --sign "$CERT_INSTALLER_NAME" \ - ./.release/lume.pkg; then - log "error" "Failed to build installer package" - exit 1 -fi - -# Verify the package was created -if [ ! -f "./.release/lume.pkg" ]; then - log "error" "Package file ./.release/lume.pkg was not created" - exit 1 -fi - -log "essential" "Package created successfully" - -# Submit for notarization using stored credentials -log "essential" "Submitting for notarization..." -if [ "$LOG_LEVEL" = "minimal" ] || [ "$LOG_LEVEL" = "none" ]; then - # Minimal output - capture ID but hide details - NOTARY_OUTPUT=$(xcrun notarytool submit ./.release/lume.pkg \ - --apple-id "${APPLE_ID}" \ - --team-id "${TEAM_ID}" \ - --password "${APP_SPECIFIC_PASSWORD}" \ - --wait 2>&1) - - # Check if notarization was successful - if echo "$NOTARY_OUTPUT" | grep -q "status: Accepted"; then - log "essential" "Notarization successful!" - else - log "error" "Notarization failed. Please check logs." - log "error" "Notarization output:" - echo "$NOTARY_OUTPUT" - exit 1 - fi -else - # Normal verbose output - if ! xcrun notarytool submit ./.release/lume.pkg \ - --apple-id "${APPLE_ID}" \ - --team-id "${TEAM_ID}" \ - --password "${APP_SPECIFIC_PASSWORD}" \ - --wait; then - log "error" "Notarization failed" - exit 1 - fi -fi - -# Staple the notarization ticket -log "essential" "Stapling notarization ticket..." -if ! xcrun stapler staple ./.release/lume.pkg > /dev/null 2>&1; then - log "error" "Failed to staple notarization ticket" - exit 1 -fi - -# Create temporary directory for package extraction -EXTRACT_ROOT=$(mktemp -d) -PKG_PATH="$(pwd)/.release/lume.pkg" - -# Extract the pkg using xar -cd "$EXTRACT_ROOT" -xar -xf "$PKG_PATH" > /dev/null 2>&1 - -# Verify Payload exists before proceeding -if [ ! -f "Payload" ]; then - log "error" "Error: Payload file not found after xar extraction" - exit 1 -fi - -# Create a directory for the extracted contents -mkdir -p extracted -cd extracted - -# Extract the Payload -cat ../Payload | gunzip -dc | cpio -i > /dev/null 2>&1 - -# Verify the binary exists -if [ ! -f "usr/local/bin/lume" ]; then - log "error" "Error: lume binary not found in expected location" - exit 1 -fi - -# Get the release directory absolute path -RELEASE_DIR="$(realpath "$(dirname "$PKG_PATH")")" -log "normal" "Using release directory: $RELEASE_DIR" - -# Copy extracted lume to the release directory -cp -f usr/local/bin/lume "$RELEASE_DIR/lume" - -# Install to user-local bin directory (standard location) -USER_BIN="$HOME/.local/bin" -mkdir -p "$USER_BIN" -cp -f "$RELEASE_DIR/lume" "$USER_BIN/lume" - -# Advise user to add to PATH if not present -if ! echo "$PATH" | grep -q "$USER_BIN"; then - log "normal" "[lume build] Note: $USER_BIN is not in your PATH. Add 'export PATH=\"$USER_BIN:\$PATH\"' to your shell profile." -fi - -# Get architecture and create OS identifier -ARCH=$(uname -m) -OS_IDENTIFIER="darwin-${ARCH}" - -# Create versioned archives of the package with OS identifier in the name -log "essential" "Creating archives in $RELEASE_DIR..." -cd "$RELEASE_DIR" - -# Clean up any existing artifacts first to avoid conflicts -rm -f lume-*.tar.gz lume-*.pkg.tar.gz - -# Create version-specific archives -log "essential" "Creating version-specific archives (${VERSION})..." -# Package the binary -tar -czf "lume-${VERSION}-${OS_IDENTIFIER}.tar.gz" lume > /dev/null 2>&1 -# Package the installer -tar -czf "lume-${VERSION}-${OS_IDENTIFIER}.pkg.tar.gz" lume.pkg > /dev/null 2>&1 - -# Create sha256 checksum file -log "essential" "Generating checksums..." -shasum -a 256 lume-*.tar.gz > checksums.txt -log "essential" "Package created successfully with checksums generated." - -# Show what's in the release directory -log "essential" "Files in release directory:" -ls -la "$RELEASE_DIR" - -# Ensure correct permissions -chmod 644 "$RELEASE_DIR"/*.tar.gz "$RELEASE_DIR"/*.pkg.tar.gz "$RELEASE_DIR"/checksums.txt - -popd > /dev/null - -# Clean up -rm -rf "$TEMP_ROOT" -rm -rf "$EXTRACT_ROOT" - -log "essential" "Build and packaging completed successfully." \ No newline at end of file diff --git a/libs/lume/scripts/build/build-release.sh b/libs/lume/scripts/build/build-release.sh deleted file mode 100755 index 20b63124..00000000 --- a/libs/lume/scripts/build/build-release.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/sh - -pushd ../../ - -swift build -c release --product lume -codesign --force --entitlement ./resources/lume.entitlements --sign - .build/release/lume - -mkdir -p ./.release -cp -f .build/release/lume ./.release/lume - -# Install to user-local bin directory (standard location) -USER_BIN="$HOME/.local/bin" -mkdir -p "$USER_BIN" -cp -f ./.release/lume "$USER_BIN/lume" - -# Advise user to add to PATH if not present -if ! echo "$PATH" | grep -q "$USER_BIN"; then - echo "[lume build] Note: $USER_BIN is not in your PATH. Add 'export PATH=\"$USER_BIN:\$PATH\"' to your shell profile." -fi - -popd \ No newline at end of file diff --git a/libs/agent/README.md b/libs/python/agent/README.md similarity index 100% rename from libs/agent/README.md rename to libs/python/agent/README.md diff --git a/libs/agent/agent/__init__.py b/libs/python/agent/agent/__init__.py similarity index 100% rename from libs/agent/agent/__init__.py rename to libs/python/agent/agent/__init__.py diff --git a/libs/agent/agent/core/__init__.py b/libs/python/agent/agent/core/__init__.py similarity index 100% rename from libs/agent/agent/core/__init__.py rename to libs/python/agent/agent/core/__init__.py diff --git a/libs/agent/agent/core/agent.py b/libs/python/agent/agent/core/agent.py similarity index 100% rename from libs/agent/agent/core/agent.py rename to libs/python/agent/agent/core/agent.py diff --git a/libs/agent/agent/core/base.py b/libs/python/agent/agent/core/base.py similarity index 100% rename from libs/agent/agent/core/base.py rename to libs/python/agent/agent/core/base.py diff --git a/libs/agent/agent/core/callbacks.py b/libs/python/agent/agent/core/callbacks.py similarity index 100% rename from libs/agent/agent/core/callbacks.py rename to libs/python/agent/agent/core/callbacks.py diff --git a/libs/agent/agent/core/experiment.py b/libs/python/agent/agent/core/experiment.py similarity index 100% rename from libs/agent/agent/core/experiment.py rename to libs/python/agent/agent/core/experiment.py diff --git a/libs/agent/agent/core/factory.py b/libs/python/agent/agent/core/factory.py similarity index 100% rename from libs/agent/agent/core/factory.py rename to libs/python/agent/agent/core/factory.py diff --git a/libs/agent/agent/core/messages.py b/libs/python/agent/agent/core/messages.py similarity index 100% rename from libs/agent/agent/core/messages.py rename to libs/python/agent/agent/core/messages.py diff --git a/libs/agent/agent/core/provider_config.py b/libs/python/agent/agent/core/provider_config.py similarity index 100% rename from libs/agent/agent/core/provider_config.py rename to libs/python/agent/agent/core/provider_config.py diff --git a/libs/agent/agent/core/telemetry.py b/libs/python/agent/agent/core/telemetry.py similarity index 100% rename from libs/agent/agent/core/telemetry.py rename to libs/python/agent/agent/core/telemetry.py diff --git a/libs/agent/agent/core/tools.py b/libs/python/agent/agent/core/tools.py similarity index 100% rename from libs/agent/agent/core/tools.py rename to libs/python/agent/agent/core/tools.py diff --git a/libs/agent/agent/core/tools/__init__.py b/libs/python/agent/agent/core/tools/__init__.py similarity index 100% rename from libs/agent/agent/core/tools/__init__.py rename to libs/python/agent/agent/core/tools/__init__.py diff --git a/libs/agent/agent/core/tools/base.py b/libs/python/agent/agent/core/tools/base.py similarity index 100% rename from libs/agent/agent/core/tools/base.py rename to libs/python/agent/agent/core/tools/base.py diff --git a/libs/agent/agent/core/tools/bash.py b/libs/python/agent/agent/core/tools/bash.py similarity index 100% rename from libs/agent/agent/core/tools/bash.py rename to libs/python/agent/agent/core/tools/bash.py diff --git a/libs/agent/agent/core/tools/collection.py b/libs/python/agent/agent/core/tools/collection.py similarity index 100% rename from libs/agent/agent/core/tools/collection.py rename to libs/python/agent/agent/core/tools/collection.py diff --git a/libs/agent/agent/core/tools/computer.py b/libs/python/agent/agent/core/tools/computer.py similarity index 100% rename from libs/agent/agent/core/tools/computer.py rename to libs/python/agent/agent/core/tools/computer.py diff --git a/libs/agent/agent/core/tools/edit.py b/libs/python/agent/agent/core/tools/edit.py similarity index 100% rename from libs/agent/agent/core/tools/edit.py rename to libs/python/agent/agent/core/tools/edit.py diff --git a/libs/agent/agent/core/tools/manager.py b/libs/python/agent/agent/core/tools/manager.py similarity index 100% rename from libs/agent/agent/core/tools/manager.py rename to libs/python/agent/agent/core/tools/manager.py diff --git a/libs/agent/agent/core/types.py b/libs/python/agent/agent/core/types.py similarity index 100% rename from libs/agent/agent/core/types.py rename to libs/python/agent/agent/core/types.py diff --git a/libs/agent/agent/core/visualization.py b/libs/python/agent/agent/core/visualization.py similarity index 100% rename from libs/agent/agent/core/visualization.py rename to libs/python/agent/agent/core/visualization.py diff --git a/libs/agent/agent/providers/__init__.py b/libs/python/agent/agent/providers/__init__.py similarity index 100% rename from libs/agent/agent/providers/__init__.py rename to libs/python/agent/agent/providers/__init__.py diff --git a/libs/agent/agent/providers/anthropic/__init__.py b/libs/python/agent/agent/providers/anthropic/__init__.py similarity index 100% rename from libs/agent/agent/providers/anthropic/__init__.py rename to libs/python/agent/agent/providers/anthropic/__init__.py diff --git a/libs/agent/agent/providers/anthropic/api/client.py b/libs/python/agent/agent/providers/anthropic/api/client.py similarity index 100% rename from libs/agent/agent/providers/anthropic/api/client.py rename to libs/python/agent/agent/providers/anthropic/api/client.py diff --git a/libs/agent/agent/providers/anthropic/api/logging.py b/libs/python/agent/agent/providers/anthropic/api/logging.py similarity index 100% rename from libs/agent/agent/providers/anthropic/api/logging.py rename to libs/python/agent/agent/providers/anthropic/api/logging.py diff --git a/libs/agent/agent/providers/anthropic/api_handler.py b/libs/python/agent/agent/providers/anthropic/api_handler.py similarity index 100% rename from libs/agent/agent/providers/anthropic/api_handler.py rename to libs/python/agent/agent/providers/anthropic/api_handler.py diff --git a/libs/agent/agent/providers/anthropic/callbacks/__init__.py b/libs/python/agent/agent/providers/anthropic/callbacks/__init__.py similarity index 100% rename from libs/agent/agent/providers/anthropic/callbacks/__init__.py rename to libs/python/agent/agent/providers/anthropic/callbacks/__init__.py diff --git a/libs/agent/agent/providers/anthropic/callbacks/manager.py b/libs/python/agent/agent/providers/anthropic/callbacks/manager.py similarity index 100% rename from libs/agent/agent/providers/anthropic/callbacks/manager.py rename to libs/python/agent/agent/providers/anthropic/callbacks/manager.py diff --git a/libs/agent/agent/providers/anthropic/loop.py b/libs/python/agent/agent/providers/anthropic/loop.py similarity index 100% rename from libs/agent/agent/providers/anthropic/loop.py rename to libs/python/agent/agent/providers/anthropic/loop.py diff --git a/libs/agent/agent/providers/anthropic/prompts.py b/libs/python/agent/agent/providers/anthropic/prompts.py similarity index 100% rename from libs/agent/agent/providers/anthropic/prompts.py rename to libs/python/agent/agent/providers/anthropic/prompts.py diff --git a/libs/agent/agent/providers/anthropic/response_handler.py b/libs/python/agent/agent/providers/anthropic/response_handler.py similarity index 100% rename from libs/agent/agent/providers/anthropic/response_handler.py rename to libs/python/agent/agent/providers/anthropic/response_handler.py diff --git a/libs/agent/agent/providers/anthropic/tools/__init__.py b/libs/python/agent/agent/providers/anthropic/tools/__init__.py similarity index 100% rename from libs/agent/agent/providers/anthropic/tools/__init__.py rename to libs/python/agent/agent/providers/anthropic/tools/__init__.py diff --git a/libs/agent/agent/providers/anthropic/tools/base.py b/libs/python/agent/agent/providers/anthropic/tools/base.py similarity index 100% rename from libs/agent/agent/providers/anthropic/tools/base.py rename to libs/python/agent/agent/providers/anthropic/tools/base.py diff --git a/libs/agent/agent/providers/anthropic/tools/bash.py b/libs/python/agent/agent/providers/anthropic/tools/bash.py similarity index 100% rename from libs/agent/agent/providers/anthropic/tools/bash.py rename to libs/python/agent/agent/providers/anthropic/tools/bash.py diff --git a/libs/agent/agent/providers/anthropic/tools/collection.py b/libs/python/agent/agent/providers/anthropic/tools/collection.py similarity index 100% rename from libs/agent/agent/providers/anthropic/tools/collection.py rename to libs/python/agent/agent/providers/anthropic/tools/collection.py diff --git a/libs/agent/agent/providers/anthropic/tools/computer.py b/libs/python/agent/agent/providers/anthropic/tools/computer.py similarity index 100% rename from libs/agent/agent/providers/anthropic/tools/computer.py rename to libs/python/agent/agent/providers/anthropic/tools/computer.py diff --git a/libs/agent/agent/providers/anthropic/tools/edit.py b/libs/python/agent/agent/providers/anthropic/tools/edit.py similarity index 100% rename from libs/agent/agent/providers/anthropic/tools/edit.py rename to libs/python/agent/agent/providers/anthropic/tools/edit.py diff --git a/libs/agent/agent/providers/anthropic/tools/manager.py b/libs/python/agent/agent/providers/anthropic/tools/manager.py similarity index 100% rename from libs/agent/agent/providers/anthropic/tools/manager.py rename to libs/python/agent/agent/providers/anthropic/tools/manager.py diff --git a/libs/agent/agent/providers/anthropic/tools/run.py b/libs/python/agent/agent/providers/anthropic/tools/run.py similarity index 100% rename from libs/agent/agent/providers/anthropic/tools/run.py rename to libs/python/agent/agent/providers/anthropic/tools/run.py diff --git a/libs/agent/agent/providers/anthropic/types.py b/libs/python/agent/agent/providers/anthropic/types.py similarity index 100% rename from libs/agent/agent/providers/anthropic/types.py rename to libs/python/agent/agent/providers/anthropic/types.py diff --git a/libs/agent/agent/providers/anthropic/utils.py b/libs/python/agent/agent/providers/anthropic/utils.py similarity index 100% rename from libs/agent/agent/providers/anthropic/utils.py rename to libs/python/agent/agent/providers/anthropic/utils.py diff --git a/libs/agent/agent/providers/omni/__init__.py b/libs/python/agent/agent/providers/omni/__init__.py similarity index 100% rename from libs/agent/agent/providers/omni/__init__.py rename to libs/python/agent/agent/providers/omni/__init__.py diff --git a/libs/agent/agent/providers/omni/api_handler.py b/libs/python/agent/agent/providers/omni/api_handler.py similarity index 100% rename from libs/agent/agent/providers/omni/api_handler.py rename to libs/python/agent/agent/providers/omni/api_handler.py diff --git a/libs/agent/agent/providers/omni/clients/anthropic.py b/libs/python/agent/agent/providers/omni/clients/anthropic.py similarity index 100% rename from libs/agent/agent/providers/omni/clients/anthropic.py rename to libs/python/agent/agent/providers/omni/clients/anthropic.py diff --git a/libs/agent/agent/providers/omni/clients/base.py b/libs/python/agent/agent/providers/omni/clients/base.py similarity index 100% rename from libs/agent/agent/providers/omni/clients/base.py rename to libs/python/agent/agent/providers/omni/clients/base.py diff --git a/libs/agent/agent/providers/omni/clients/oaicompat.py b/libs/python/agent/agent/providers/omni/clients/oaicompat.py similarity index 100% rename from libs/agent/agent/providers/omni/clients/oaicompat.py rename to libs/python/agent/agent/providers/omni/clients/oaicompat.py diff --git a/libs/agent/agent/providers/omni/clients/ollama.py b/libs/python/agent/agent/providers/omni/clients/ollama.py similarity index 100% rename from libs/agent/agent/providers/omni/clients/ollama.py rename to libs/python/agent/agent/providers/omni/clients/ollama.py diff --git a/libs/agent/agent/providers/omni/clients/openai.py b/libs/python/agent/agent/providers/omni/clients/openai.py similarity index 100% rename from libs/agent/agent/providers/omni/clients/openai.py rename to libs/python/agent/agent/providers/omni/clients/openai.py diff --git a/libs/agent/agent/providers/omni/clients/utils.py b/libs/python/agent/agent/providers/omni/clients/utils.py similarity index 100% rename from libs/agent/agent/providers/omni/clients/utils.py rename to libs/python/agent/agent/providers/omni/clients/utils.py diff --git a/libs/agent/agent/providers/omni/image_utils.py b/libs/python/agent/agent/providers/omni/image_utils.py similarity index 100% rename from libs/agent/agent/providers/omni/image_utils.py rename to libs/python/agent/agent/providers/omni/image_utils.py diff --git a/libs/agent/agent/providers/omni/loop.py b/libs/python/agent/agent/providers/omni/loop.py similarity index 100% rename from libs/agent/agent/providers/omni/loop.py rename to libs/python/agent/agent/providers/omni/loop.py diff --git a/libs/agent/agent/providers/omni/parser.py b/libs/python/agent/agent/providers/omni/parser.py similarity index 100% rename from libs/agent/agent/providers/omni/parser.py rename to libs/python/agent/agent/providers/omni/parser.py diff --git a/libs/agent/agent/providers/omni/prompts.py b/libs/python/agent/agent/providers/omni/prompts.py similarity index 100% rename from libs/agent/agent/providers/omni/prompts.py rename to libs/python/agent/agent/providers/omni/prompts.py diff --git a/libs/agent/agent/providers/omni/tools/__init__.py b/libs/python/agent/agent/providers/omni/tools/__init__.py similarity index 100% rename from libs/agent/agent/providers/omni/tools/__init__.py rename to libs/python/agent/agent/providers/omni/tools/__init__.py diff --git a/libs/agent/agent/providers/omni/tools/base.py b/libs/python/agent/agent/providers/omni/tools/base.py similarity index 100% rename from libs/agent/agent/providers/omni/tools/base.py rename to libs/python/agent/agent/providers/omni/tools/base.py diff --git a/libs/agent/agent/providers/omni/tools/bash.py b/libs/python/agent/agent/providers/omni/tools/bash.py similarity index 100% rename from libs/agent/agent/providers/omni/tools/bash.py rename to libs/python/agent/agent/providers/omni/tools/bash.py diff --git a/libs/agent/agent/providers/omni/tools/computer.py b/libs/python/agent/agent/providers/omni/tools/computer.py similarity index 100% rename from libs/agent/agent/providers/omni/tools/computer.py rename to libs/python/agent/agent/providers/omni/tools/computer.py diff --git a/libs/agent/agent/providers/omni/tools/manager.py b/libs/python/agent/agent/providers/omni/tools/manager.py similarity index 100% rename from libs/agent/agent/providers/omni/tools/manager.py rename to libs/python/agent/agent/providers/omni/tools/manager.py diff --git a/libs/agent/agent/providers/omni/utils.py b/libs/python/agent/agent/providers/omni/utils.py similarity index 100% rename from libs/agent/agent/providers/omni/utils.py rename to libs/python/agent/agent/providers/omni/utils.py diff --git a/libs/agent/agent/providers/openai/__init__.py b/libs/python/agent/agent/providers/openai/__init__.py similarity index 100% rename from libs/agent/agent/providers/openai/__init__.py rename to libs/python/agent/agent/providers/openai/__init__.py diff --git a/libs/agent/agent/providers/openai/api_handler.py b/libs/python/agent/agent/providers/openai/api_handler.py similarity index 100% rename from libs/agent/agent/providers/openai/api_handler.py rename to libs/python/agent/agent/providers/openai/api_handler.py diff --git a/libs/agent/agent/providers/openai/loop.py b/libs/python/agent/agent/providers/openai/loop.py similarity index 100% rename from libs/agent/agent/providers/openai/loop.py rename to libs/python/agent/agent/providers/openai/loop.py diff --git a/libs/agent/agent/providers/openai/response_handler.py b/libs/python/agent/agent/providers/openai/response_handler.py similarity index 100% rename from libs/agent/agent/providers/openai/response_handler.py rename to libs/python/agent/agent/providers/openai/response_handler.py diff --git a/libs/agent/agent/providers/openai/tools/__init__.py b/libs/python/agent/agent/providers/openai/tools/__init__.py similarity index 100% rename from libs/agent/agent/providers/openai/tools/__init__.py rename to libs/python/agent/agent/providers/openai/tools/__init__.py diff --git a/libs/agent/agent/providers/openai/tools/base.py b/libs/python/agent/agent/providers/openai/tools/base.py similarity index 100% rename from libs/agent/agent/providers/openai/tools/base.py rename to libs/python/agent/agent/providers/openai/tools/base.py diff --git a/libs/agent/agent/providers/openai/tools/computer.py b/libs/python/agent/agent/providers/openai/tools/computer.py similarity index 100% rename from libs/agent/agent/providers/openai/tools/computer.py rename to libs/python/agent/agent/providers/openai/tools/computer.py diff --git a/libs/agent/agent/providers/openai/tools/manager.py b/libs/python/agent/agent/providers/openai/tools/manager.py similarity index 100% rename from libs/agent/agent/providers/openai/tools/manager.py rename to libs/python/agent/agent/providers/openai/tools/manager.py diff --git a/libs/agent/agent/providers/openai/types.py b/libs/python/agent/agent/providers/openai/types.py similarity index 100% rename from libs/agent/agent/providers/openai/types.py rename to libs/python/agent/agent/providers/openai/types.py diff --git a/libs/agent/agent/providers/openai/utils.py b/libs/python/agent/agent/providers/openai/utils.py similarity index 100% rename from libs/agent/agent/providers/openai/utils.py rename to libs/python/agent/agent/providers/openai/utils.py diff --git a/libs/agent/agent/providers/uitars/__init__.py b/libs/python/agent/agent/providers/uitars/__init__.py similarity index 100% rename from libs/agent/agent/providers/uitars/__init__.py rename to libs/python/agent/agent/providers/uitars/__init__.py diff --git a/libs/agent/agent/providers/uitars/clients/base.py b/libs/python/agent/agent/providers/uitars/clients/base.py similarity index 100% rename from libs/agent/agent/providers/uitars/clients/base.py rename to libs/python/agent/agent/providers/uitars/clients/base.py diff --git a/libs/agent/agent/providers/uitars/clients/mlxvlm.py b/libs/python/agent/agent/providers/uitars/clients/mlxvlm.py similarity index 100% rename from libs/agent/agent/providers/uitars/clients/mlxvlm.py rename to libs/python/agent/agent/providers/uitars/clients/mlxvlm.py diff --git a/libs/agent/agent/providers/uitars/clients/oaicompat.py b/libs/python/agent/agent/providers/uitars/clients/oaicompat.py similarity index 100% rename from libs/agent/agent/providers/uitars/clients/oaicompat.py rename to libs/python/agent/agent/providers/uitars/clients/oaicompat.py diff --git a/libs/agent/agent/providers/uitars/loop.py b/libs/python/agent/agent/providers/uitars/loop.py similarity index 100% rename from libs/agent/agent/providers/uitars/loop.py rename to libs/python/agent/agent/providers/uitars/loop.py diff --git a/libs/agent/agent/providers/uitars/prompts.py b/libs/python/agent/agent/providers/uitars/prompts.py similarity index 100% rename from libs/agent/agent/providers/uitars/prompts.py rename to libs/python/agent/agent/providers/uitars/prompts.py diff --git a/libs/agent/agent/providers/uitars/tools/__init__.py b/libs/python/agent/agent/providers/uitars/tools/__init__.py similarity index 100% rename from libs/agent/agent/providers/uitars/tools/__init__.py rename to libs/python/agent/agent/providers/uitars/tools/__init__.py diff --git a/libs/agent/agent/providers/uitars/tools/computer.py b/libs/python/agent/agent/providers/uitars/tools/computer.py similarity index 100% rename from libs/agent/agent/providers/uitars/tools/computer.py rename to libs/python/agent/agent/providers/uitars/tools/computer.py diff --git a/libs/agent/agent/providers/uitars/tools/manager.py b/libs/python/agent/agent/providers/uitars/tools/manager.py similarity index 100% rename from libs/agent/agent/providers/uitars/tools/manager.py rename to libs/python/agent/agent/providers/uitars/tools/manager.py diff --git a/libs/agent/agent/providers/uitars/utils.py b/libs/python/agent/agent/providers/uitars/utils.py similarity index 100% rename from libs/agent/agent/providers/uitars/utils.py rename to libs/python/agent/agent/providers/uitars/utils.py diff --git a/libs/agent/agent/telemetry.py b/libs/python/agent/agent/telemetry.py similarity index 100% rename from libs/agent/agent/telemetry.py rename to libs/python/agent/agent/telemetry.py diff --git a/libs/agent/agent/ui/__init__.py b/libs/python/agent/agent/ui/__init__.py similarity index 100% rename from libs/agent/agent/ui/__init__.py rename to libs/python/agent/agent/ui/__init__.py diff --git a/libs/agent/agent/ui/__main__.py b/libs/python/agent/agent/ui/__main__.py similarity index 100% rename from libs/agent/agent/ui/__main__.py rename to libs/python/agent/agent/ui/__main__.py diff --git a/libs/agent/agent/ui/gradio/__init__.py b/libs/python/agent/agent/ui/gradio/__init__.py similarity index 100% rename from libs/agent/agent/ui/gradio/__init__.py rename to libs/python/agent/agent/ui/gradio/__init__.py diff --git a/libs/agent/agent/ui/gradio/app.py b/libs/python/agent/agent/ui/gradio/app.py similarity index 100% rename from libs/agent/agent/ui/gradio/app.py rename to libs/python/agent/agent/ui/gradio/app.py diff --git a/libs/agent/poetry.toml b/libs/python/agent/poetry.toml similarity index 100% rename from libs/agent/poetry.toml rename to libs/python/agent/poetry.toml diff --git a/libs/agent/pyproject.toml b/libs/python/agent/pyproject.toml similarity index 100% rename from libs/agent/pyproject.toml rename to libs/python/agent/pyproject.toml diff --git a/libs/computer-server/README.md b/libs/python/computer-server/README.md similarity index 100% rename from libs/computer-server/README.md rename to libs/python/computer-server/README.md diff --git a/libs/computer-server/computer_server/__init__.py b/libs/python/computer-server/computer_server/__init__.py similarity index 100% rename from libs/computer-server/computer_server/__init__.py rename to libs/python/computer-server/computer_server/__init__.py diff --git a/libs/computer-server/computer_server/__main__.py b/libs/python/computer-server/computer_server/__main__.py similarity index 100% rename from libs/computer-server/computer_server/__main__.py rename to libs/python/computer-server/computer_server/__main__.py diff --git a/libs/computer-server/computer_server/cli.py b/libs/python/computer-server/computer_server/cli.py similarity index 100% rename from libs/computer-server/computer_server/cli.py rename to libs/python/computer-server/computer_server/cli.py diff --git a/libs/computer-server/computer_server/diorama/__init__.py b/libs/python/computer-server/computer_server/diorama/__init__.py similarity index 100% rename from libs/computer-server/computer_server/diorama/__init__.py rename to libs/python/computer-server/computer_server/diorama/__init__.py diff --git a/libs/computer-server/computer_server/diorama/base.py b/libs/python/computer-server/computer_server/diorama/base.py similarity index 100% rename from libs/computer-server/computer_server/diorama/base.py rename to libs/python/computer-server/computer_server/diorama/base.py diff --git a/libs/computer-server/computer_server/diorama/diorama.py b/libs/python/computer-server/computer_server/diorama/diorama.py similarity index 100% rename from libs/computer-server/computer_server/diorama/diorama.py rename to libs/python/computer-server/computer_server/diorama/diorama.py diff --git a/libs/computer-server/computer_server/diorama/diorama_computer.py b/libs/python/computer-server/computer_server/diorama/diorama_computer.py similarity index 100% rename from libs/computer-server/computer_server/diorama/diorama_computer.py rename to libs/python/computer-server/computer_server/diorama/diorama_computer.py diff --git a/libs/computer-server/computer_server/diorama/draw.py b/libs/python/computer-server/computer_server/diorama/draw.py similarity index 100% rename from libs/computer-server/computer_server/diorama/draw.py rename to libs/python/computer-server/computer_server/diorama/draw.py diff --git a/libs/computer-server/computer_server/diorama/macos.py b/libs/python/computer-server/computer_server/diorama/macos.py similarity index 100% rename from libs/computer-server/computer_server/diorama/macos.py rename to libs/python/computer-server/computer_server/diorama/macos.py diff --git a/libs/computer-server/computer_server/diorama/safezone.py b/libs/python/computer-server/computer_server/diorama/safezone.py similarity index 100% rename from libs/computer-server/computer_server/diorama/safezone.py rename to libs/python/computer-server/computer_server/diorama/safezone.py diff --git a/libs/computer-server/computer_server/handlers/base.py b/libs/python/computer-server/computer_server/handlers/base.py similarity index 100% rename from libs/computer-server/computer_server/handlers/base.py rename to libs/python/computer-server/computer_server/handlers/base.py diff --git a/libs/computer-server/computer_server/handlers/factory.py b/libs/python/computer-server/computer_server/handlers/factory.py similarity index 100% rename from libs/computer-server/computer_server/handlers/factory.py rename to libs/python/computer-server/computer_server/handlers/factory.py diff --git a/libs/computer-server/computer_server/handlers/generic.py b/libs/python/computer-server/computer_server/handlers/generic.py similarity index 100% rename from libs/computer-server/computer_server/handlers/generic.py rename to libs/python/computer-server/computer_server/handlers/generic.py diff --git a/libs/computer-server/computer_server/handlers/linux.py b/libs/python/computer-server/computer_server/handlers/linux.py similarity index 100% rename from libs/computer-server/computer_server/handlers/linux.py rename to libs/python/computer-server/computer_server/handlers/linux.py diff --git a/libs/computer-server/computer_server/handlers/macos.py b/libs/python/computer-server/computer_server/handlers/macos.py similarity index 100% rename from libs/computer-server/computer_server/handlers/macos.py rename to libs/python/computer-server/computer_server/handlers/macos.py diff --git a/libs/computer-server/computer_server/handlers/windows.py b/libs/python/computer-server/computer_server/handlers/windows.py similarity index 100% rename from libs/computer-server/computer_server/handlers/windows.py rename to libs/python/computer-server/computer_server/handlers/windows.py diff --git a/libs/computer-server/computer_server/main.py b/libs/python/computer-server/computer_server/main.py similarity index 100% rename from libs/computer-server/computer_server/main.py rename to libs/python/computer-server/computer_server/main.py diff --git a/libs/computer-server/computer_server/server.py b/libs/python/computer-server/computer_server/server.py similarity index 100% rename from libs/computer-server/computer_server/server.py rename to libs/python/computer-server/computer_server/server.py diff --git a/libs/computer-server/examples/__init__.py b/libs/python/computer-server/examples/__init__.py similarity index 100% rename from libs/computer-server/examples/__init__.py rename to libs/python/computer-server/examples/__init__.py diff --git a/libs/computer-server/examples/usage_example.py b/libs/python/computer-server/examples/usage_example.py similarity index 100% rename from libs/computer-server/examples/usage_example.py rename to libs/python/computer-server/examples/usage_example.py diff --git a/libs/computer-server/pyproject.toml b/libs/python/computer-server/pyproject.toml similarity index 100% rename from libs/computer-server/pyproject.toml rename to libs/python/computer-server/pyproject.toml diff --git a/libs/computer-server/run_server.py b/libs/python/computer-server/run_server.py similarity index 100% rename from libs/computer-server/run_server.py rename to libs/python/computer-server/run_server.py diff --git a/libs/computer-server/test_connection.py b/libs/python/computer-server/test_connection.py similarity index 100% rename from libs/computer-server/test_connection.py rename to libs/python/computer-server/test_connection.py diff --git a/libs/computer/python/README.md b/libs/python/computer/README.md similarity index 100% rename from libs/computer/python/README.md rename to libs/python/computer/README.md diff --git a/libs/computer/python/computer/__init__.py b/libs/python/computer/computer/__init__.py similarity index 100% rename from libs/computer/python/computer/__init__.py rename to libs/python/computer/computer/__init__.py diff --git a/libs/computer/python/computer/computer.py b/libs/python/computer/computer/computer.py similarity index 100% rename from libs/computer/python/computer/computer.py rename to libs/python/computer/computer/computer.py diff --git a/libs/computer/python/computer/diorama_computer.py b/libs/python/computer/computer/diorama_computer.py similarity index 100% rename from libs/computer/python/computer/diorama_computer.py rename to libs/python/computer/computer/diorama_computer.py diff --git a/libs/computer/python/computer/helpers.py b/libs/python/computer/computer/helpers.py similarity index 100% rename from libs/computer/python/computer/helpers.py rename to libs/python/computer/computer/helpers.py diff --git a/libs/computer/python/computer/interface/__init__.py b/libs/python/computer/computer/interface/__init__.py similarity index 100% rename from libs/computer/python/computer/interface/__init__.py rename to libs/python/computer/computer/interface/__init__.py diff --git a/libs/computer/python/computer/interface/base.py b/libs/python/computer/computer/interface/base.py similarity index 100% rename from libs/computer/python/computer/interface/base.py rename to libs/python/computer/computer/interface/base.py diff --git a/libs/computer/python/computer/interface/factory.py b/libs/python/computer/computer/interface/factory.py similarity index 100% rename from libs/computer/python/computer/interface/factory.py rename to libs/python/computer/computer/interface/factory.py diff --git a/libs/computer/python/computer/interface/linux.py b/libs/python/computer/computer/interface/linux.py similarity index 100% rename from libs/computer/python/computer/interface/linux.py rename to libs/python/computer/computer/interface/linux.py diff --git a/libs/computer/python/computer/interface/macos.py b/libs/python/computer/computer/interface/macos.py similarity index 100% rename from libs/computer/python/computer/interface/macos.py rename to libs/python/computer/computer/interface/macos.py diff --git a/libs/computer/python/computer/interface/models.py b/libs/python/computer/computer/interface/models.py similarity index 100% rename from libs/computer/python/computer/interface/models.py rename to libs/python/computer/computer/interface/models.py diff --git a/libs/computer/python/computer/interface/windows.py b/libs/python/computer/computer/interface/windows.py similarity index 100% rename from libs/computer/python/computer/interface/windows.py rename to libs/python/computer/computer/interface/windows.py diff --git a/libs/computer/python/computer/logger.py b/libs/python/computer/computer/logger.py similarity index 100% rename from libs/computer/python/computer/logger.py rename to libs/python/computer/computer/logger.py diff --git a/libs/computer/python/computer/models.py b/libs/python/computer/computer/models.py similarity index 100% rename from libs/computer/python/computer/models.py rename to libs/python/computer/computer/models.py diff --git a/libs/computer/python/computer/providers/__init__.py b/libs/python/computer/computer/providers/__init__.py similarity index 100% rename from libs/computer/python/computer/providers/__init__.py rename to libs/python/computer/computer/providers/__init__.py diff --git a/libs/computer/python/computer/providers/base.py b/libs/python/computer/computer/providers/base.py similarity index 100% rename from libs/computer/python/computer/providers/base.py rename to libs/python/computer/computer/providers/base.py diff --git a/libs/computer/python/computer/providers/cloud/__init__.py b/libs/python/computer/computer/providers/cloud/__init__.py similarity index 100% rename from libs/computer/python/computer/providers/cloud/__init__.py rename to libs/python/computer/computer/providers/cloud/__init__.py diff --git a/libs/computer/python/computer/providers/cloud/provider.py b/libs/python/computer/computer/providers/cloud/provider.py similarity index 100% rename from libs/computer/python/computer/providers/cloud/provider.py rename to libs/python/computer/computer/providers/cloud/provider.py diff --git a/libs/computer/python/computer/providers/factory.py b/libs/python/computer/computer/providers/factory.py similarity index 100% rename from libs/computer/python/computer/providers/factory.py rename to libs/python/computer/computer/providers/factory.py diff --git a/libs/computer/python/computer/providers/lume/__init__.py b/libs/python/computer/computer/providers/lume/__init__.py similarity index 100% rename from libs/computer/python/computer/providers/lume/__init__.py rename to libs/python/computer/computer/providers/lume/__init__.py diff --git a/libs/computer/python/computer/providers/lume/provider.py b/libs/python/computer/computer/providers/lume/provider.py similarity index 100% rename from libs/computer/python/computer/providers/lume/provider.py rename to libs/python/computer/computer/providers/lume/provider.py diff --git a/libs/computer/python/computer/providers/lume_api.py b/libs/python/computer/computer/providers/lume_api.py similarity index 100% rename from libs/computer/python/computer/providers/lume_api.py rename to libs/python/computer/computer/providers/lume_api.py diff --git a/libs/computer/python/computer/providers/lumier/__init__.py b/libs/python/computer/computer/providers/lumier/__init__.py similarity index 100% rename from libs/computer/python/computer/providers/lumier/__init__.py rename to libs/python/computer/computer/providers/lumier/__init__.py diff --git a/libs/computer/python/computer/providers/lumier/provider.py b/libs/python/computer/computer/providers/lumier/provider.py similarity index 100% rename from libs/computer/python/computer/providers/lumier/provider.py rename to libs/python/computer/computer/providers/lumier/provider.py diff --git a/libs/computer/python/computer/providers/winsandbox/__init__.py b/libs/python/computer/computer/providers/winsandbox/__init__.py similarity index 100% rename from libs/computer/python/computer/providers/winsandbox/__init__.py rename to libs/python/computer/computer/providers/winsandbox/__init__.py diff --git a/libs/computer/python/computer/providers/winsandbox/provider.py b/libs/python/computer/computer/providers/winsandbox/provider.py similarity index 100% rename from libs/computer/python/computer/providers/winsandbox/provider.py rename to libs/python/computer/computer/providers/winsandbox/provider.py diff --git a/libs/computer/python/computer/providers/winsandbox/setup_script.ps1 b/libs/python/computer/computer/providers/winsandbox/setup_script.ps1 similarity index 100% rename from libs/computer/python/computer/providers/winsandbox/setup_script.ps1 rename to libs/python/computer/computer/providers/winsandbox/setup_script.ps1 diff --git a/libs/computer/python/computer/telemetry.py b/libs/python/computer/computer/telemetry.py similarity index 100% rename from libs/computer/python/computer/telemetry.py rename to libs/python/computer/computer/telemetry.py diff --git a/libs/computer/python/computer/ui/__init__.py b/libs/python/computer/computer/ui/__init__.py similarity index 100% rename from libs/computer/python/computer/ui/__init__.py rename to libs/python/computer/computer/ui/__init__.py diff --git a/libs/computer/python/computer/ui/__main__.py b/libs/python/computer/computer/ui/__main__.py similarity index 100% rename from libs/computer/python/computer/ui/__main__.py rename to libs/python/computer/computer/ui/__main__.py diff --git a/libs/computer/python/computer/ui/gradio/__init__.py b/libs/python/computer/computer/ui/gradio/__init__.py similarity index 100% rename from libs/computer/python/computer/ui/gradio/__init__.py rename to libs/python/computer/computer/ui/gradio/__init__.py diff --git a/libs/computer/python/computer/ui/gradio/app.py b/libs/python/computer/computer/ui/gradio/app.py similarity index 100% rename from libs/computer/python/computer/ui/gradio/app.py rename to libs/python/computer/computer/ui/gradio/app.py diff --git a/libs/computer/python/computer/utils.py b/libs/python/computer/computer/utils.py similarity index 100% rename from libs/computer/python/computer/utils.py rename to libs/python/computer/computer/utils.py diff --git a/libs/computer/python/poetry.toml b/libs/python/computer/poetry.toml similarity index 100% rename from libs/computer/python/poetry.toml rename to libs/python/computer/poetry.toml diff --git a/libs/computer/python/pyproject.toml b/libs/python/computer/pyproject.toml similarity index 100% rename from libs/computer/python/pyproject.toml rename to libs/python/computer/pyproject.toml diff --git a/libs/core/README.md b/libs/python/core/README.md similarity index 100% rename from libs/core/README.md rename to libs/python/core/README.md diff --git a/libs/core/core/__init__.py b/libs/python/core/core/__init__.py similarity index 100% rename from libs/core/core/__init__.py rename to libs/python/core/core/__init__.py diff --git a/libs/core/core/telemetry/__init__.py b/libs/python/core/core/telemetry/__init__.py similarity index 100% rename from libs/core/core/telemetry/__init__.py rename to libs/python/core/core/telemetry/__init__.py diff --git a/libs/core/core/telemetry/client.py b/libs/python/core/core/telemetry/client.py similarity index 100% rename from libs/core/core/telemetry/client.py rename to libs/python/core/core/telemetry/client.py diff --git a/libs/core/core/telemetry/models.py b/libs/python/core/core/telemetry/models.py similarity index 100% rename from libs/core/core/telemetry/models.py rename to libs/python/core/core/telemetry/models.py diff --git a/libs/core/core/telemetry/posthog_client.py b/libs/python/core/core/telemetry/posthog_client.py similarity index 100% rename from libs/core/core/telemetry/posthog_client.py rename to libs/python/core/core/telemetry/posthog_client.py diff --git a/libs/core/core/telemetry/sender.py b/libs/python/core/core/telemetry/sender.py similarity index 100% rename from libs/core/core/telemetry/sender.py rename to libs/python/core/core/telemetry/sender.py diff --git a/libs/core/core/telemetry/telemetry.py b/libs/python/core/core/telemetry/telemetry.py similarity index 100% rename from libs/core/core/telemetry/telemetry.py rename to libs/python/core/core/telemetry/telemetry.py diff --git a/libs/core/poetry.toml b/libs/python/core/poetry.toml similarity index 100% rename from libs/core/poetry.toml rename to libs/python/core/poetry.toml diff --git a/libs/core/pyproject.toml b/libs/python/core/pyproject.toml similarity index 100% rename from libs/core/pyproject.toml rename to libs/python/core/pyproject.toml diff --git a/libs/lume/.cursorignore b/libs/python/lume/.cursorignore similarity index 100% rename from libs/lume/.cursorignore rename to libs/python/lume/.cursorignore diff --git a/libs/lume/CONTRIBUTING.md b/libs/python/lume/CONTRIBUTING.md similarity index 100% rename from libs/lume/CONTRIBUTING.md rename to libs/python/lume/CONTRIBUTING.md diff --git a/libs/lume/Package.resolved b/libs/python/lume/Package.resolved similarity index 100% rename from libs/lume/Package.resolved rename to libs/python/lume/Package.resolved diff --git a/libs/lume/Package.swift b/libs/python/lume/Package.swift similarity index 100% rename from libs/lume/Package.swift rename to libs/python/lume/Package.swift diff --git a/libs/lume/README.md b/libs/python/lume/README.md similarity index 100% rename from libs/lume/README.md rename to libs/python/lume/README.md diff --git a/libs/lume/docs/API-Reference.md b/libs/python/lume/docs/API-Reference.md similarity index 100% rename from libs/lume/docs/API-Reference.md rename to libs/python/lume/docs/API-Reference.md diff --git a/libs/lume/docs/Development.md b/libs/python/lume/docs/Development.md similarity index 100% rename from libs/lume/docs/Development.md rename to libs/python/lume/docs/Development.md diff --git a/libs/lume/docs/FAQ.md b/libs/python/lume/docs/FAQ.md similarity index 100% rename from libs/lume/docs/FAQ.md rename to libs/python/lume/docs/FAQ.md diff --git a/libs/lume/img/cli.png b/libs/python/lume/img/cli.png similarity index 100% rename from libs/lume/img/cli.png rename to libs/python/lume/img/cli.png diff --git a/libs/lume/img/logo_black.png b/libs/python/lume/img/logo_black.png similarity index 100% rename from libs/lume/img/logo_black.png rename to libs/python/lume/img/logo_black.png diff --git a/libs/lume/img/logo_white.png b/libs/python/lume/img/logo_white.png similarity index 100% rename from libs/lume/img/logo_white.png rename to libs/python/lume/img/logo_white.png diff --git a/libs/lume/resources/lume.entitlements b/libs/python/lume/resources/lume.entitlements similarity index 100% rename from libs/lume/resources/lume.entitlements rename to libs/python/lume/resources/lume.entitlements diff --git a/libs/lume/scripts/install.sh b/libs/python/lume/scripts/install.sh similarity index 100% rename from libs/lume/scripts/install.sh rename to libs/python/lume/scripts/install.sh diff --git a/libs/lume/src/Commands/Clone.swift b/libs/python/lume/src/Commands/Clone.swift similarity index 100% rename from libs/lume/src/Commands/Clone.swift rename to libs/python/lume/src/Commands/Clone.swift diff --git a/libs/lume/src/Commands/Config.swift b/libs/python/lume/src/Commands/Config.swift similarity index 100% rename from libs/lume/src/Commands/Config.swift rename to libs/python/lume/src/Commands/Config.swift diff --git a/libs/lume/src/Commands/Create.swift b/libs/python/lume/src/Commands/Create.swift similarity index 100% rename from libs/lume/src/Commands/Create.swift rename to libs/python/lume/src/Commands/Create.swift diff --git a/libs/lume/src/Commands/Delete.swift b/libs/python/lume/src/Commands/Delete.swift similarity index 100% rename from libs/lume/src/Commands/Delete.swift rename to libs/python/lume/src/Commands/Delete.swift diff --git a/libs/lume/src/Commands/Get.swift b/libs/python/lume/src/Commands/Get.swift similarity index 100% rename from libs/lume/src/Commands/Get.swift rename to libs/python/lume/src/Commands/Get.swift diff --git a/libs/lume/src/Commands/IPSW.swift b/libs/python/lume/src/Commands/IPSW.swift similarity index 100% rename from libs/lume/src/Commands/IPSW.swift rename to libs/python/lume/src/Commands/IPSW.swift diff --git a/libs/lume/src/Commands/Images.swift b/libs/python/lume/src/Commands/Images.swift similarity index 100% rename from libs/lume/src/Commands/Images.swift rename to libs/python/lume/src/Commands/Images.swift diff --git a/libs/lume/src/Commands/List.swift b/libs/python/lume/src/Commands/List.swift similarity index 100% rename from libs/lume/src/Commands/List.swift rename to libs/python/lume/src/Commands/List.swift diff --git a/libs/lume/src/Commands/Logs.swift b/libs/python/lume/src/Commands/Logs.swift similarity index 100% rename from libs/lume/src/Commands/Logs.swift rename to libs/python/lume/src/Commands/Logs.swift diff --git a/libs/lume/src/Commands/Options/FormatOption.swift b/libs/python/lume/src/Commands/Options/FormatOption.swift similarity index 100% rename from libs/lume/src/Commands/Options/FormatOption.swift rename to libs/python/lume/src/Commands/Options/FormatOption.swift diff --git a/libs/lume/src/Commands/Prune.swift b/libs/python/lume/src/Commands/Prune.swift similarity index 100% rename from libs/lume/src/Commands/Prune.swift rename to libs/python/lume/src/Commands/Prune.swift diff --git a/libs/lume/src/Commands/Pull.swift b/libs/python/lume/src/Commands/Pull.swift similarity index 100% rename from libs/lume/src/Commands/Pull.swift rename to libs/python/lume/src/Commands/Pull.swift diff --git a/libs/lume/src/Commands/Push.swift b/libs/python/lume/src/Commands/Push.swift similarity index 100% rename from libs/lume/src/Commands/Push.swift rename to libs/python/lume/src/Commands/Push.swift diff --git a/libs/lume/src/Commands/Run.swift b/libs/python/lume/src/Commands/Run.swift similarity index 100% rename from libs/lume/src/Commands/Run.swift rename to libs/python/lume/src/Commands/Run.swift diff --git a/libs/lume/src/Commands/Serve.swift b/libs/python/lume/src/Commands/Serve.swift similarity index 100% rename from libs/lume/src/Commands/Serve.swift rename to libs/python/lume/src/Commands/Serve.swift diff --git a/libs/lume/src/Commands/Set.swift b/libs/python/lume/src/Commands/Set.swift similarity index 100% rename from libs/lume/src/Commands/Set.swift rename to libs/python/lume/src/Commands/Set.swift diff --git a/libs/lume/src/Commands/Stop.swift b/libs/python/lume/src/Commands/Stop.swift similarity index 100% rename from libs/lume/src/Commands/Stop.swift rename to libs/python/lume/src/Commands/Stop.swift diff --git a/libs/lume/src/ContainerRegistry/ImageContainerRegistry.swift b/libs/python/lume/src/ContainerRegistry/ImageContainerRegistry.swift similarity index 100% rename from libs/lume/src/ContainerRegistry/ImageContainerRegistry.swift rename to libs/python/lume/src/ContainerRegistry/ImageContainerRegistry.swift diff --git a/libs/lume/src/ContainerRegistry/ImageList.swift b/libs/python/lume/src/ContainerRegistry/ImageList.swift similarity index 100% rename from libs/lume/src/ContainerRegistry/ImageList.swift rename to libs/python/lume/src/ContainerRegistry/ImageList.swift diff --git a/libs/lume/src/ContainerRegistry/ImagesPrinter.swift b/libs/python/lume/src/ContainerRegistry/ImagesPrinter.swift similarity index 100% rename from libs/lume/src/ContainerRegistry/ImagesPrinter.swift rename to libs/python/lume/src/ContainerRegistry/ImagesPrinter.swift diff --git a/libs/lume/src/Errors/Errors.swift b/libs/python/lume/src/Errors/Errors.swift similarity index 100% rename from libs/lume/src/Errors/Errors.swift rename to libs/python/lume/src/Errors/Errors.swift diff --git a/libs/lume/src/FileSystem/Home.swift b/libs/python/lume/src/FileSystem/Home.swift similarity index 100% rename from libs/lume/src/FileSystem/Home.swift rename to libs/python/lume/src/FileSystem/Home.swift diff --git a/libs/lume/src/FileSystem/Settings.swift b/libs/python/lume/src/FileSystem/Settings.swift similarity index 100% rename from libs/lume/src/FileSystem/Settings.swift rename to libs/python/lume/src/FileSystem/Settings.swift diff --git a/libs/lume/src/FileSystem/VMConfig.swift b/libs/python/lume/src/FileSystem/VMConfig.swift similarity index 100% rename from libs/lume/src/FileSystem/VMConfig.swift rename to libs/python/lume/src/FileSystem/VMConfig.swift diff --git a/libs/lume/src/FileSystem/VMDirectory.swift b/libs/python/lume/src/FileSystem/VMDirectory.swift similarity index 100% rename from libs/lume/src/FileSystem/VMDirectory.swift rename to libs/python/lume/src/FileSystem/VMDirectory.swift diff --git a/libs/lume/src/FileSystem/VMLocation.swift b/libs/python/lume/src/FileSystem/VMLocation.swift similarity index 100% rename from libs/lume/src/FileSystem/VMLocation.swift rename to libs/python/lume/src/FileSystem/VMLocation.swift diff --git a/libs/lume/src/LumeController.swift b/libs/python/lume/src/LumeController.swift similarity index 100% rename from libs/lume/src/LumeController.swift rename to libs/python/lume/src/LumeController.swift diff --git a/libs/lume/src/Main.swift b/libs/python/lume/src/Main.swift similarity index 100% rename from libs/lume/src/Main.swift rename to libs/python/lume/src/Main.swift diff --git a/libs/lume/src/Server/HTTP.swift b/libs/python/lume/src/Server/HTTP.swift similarity index 100% rename from libs/lume/src/Server/HTTP.swift rename to libs/python/lume/src/Server/HTTP.swift diff --git a/libs/lume/src/Server/Handlers.swift b/libs/python/lume/src/Server/Handlers.swift similarity index 100% rename from libs/lume/src/Server/Handlers.swift rename to libs/python/lume/src/Server/Handlers.swift diff --git a/libs/lume/src/Server/Requests.swift b/libs/python/lume/src/Server/Requests.swift similarity index 100% rename from libs/lume/src/Server/Requests.swift rename to libs/python/lume/src/Server/Requests.swift diff --git a/libs/lume/src/Server/Responses.swift b/libs/python/lume/src/Server/Responses.swift similarity index 100% rename from libs/lume/src/Server/Responses.swift rename to libs/python/lume/src/Server/Responses.swift diff --git a/libs/lume/src/Server/Server.swift b/libs/python/lume/src/Server/Server.swift similarity index 100% rename from libs/lume/src/Server/Server.swift rename to libs/python/lume/src/Server/Server.swift diff --git a/libs/lume/src/Utils/CommandRegistry.swift b/libs/python/lume/src/Utils/CommandRegistry.swift similarity index 100% rename from libs/lume/src/Utils/CommandRegistry.swift rename to libs/python/lume/src/Utils/CommandRegistry.swift diff --git a/libs/lume/src/Utils/CommandUtils.swift b/libs/python/lume/src/Utils/CommandUtils.swift similarity index 100% rename from libs/lume/src/Utils/CommandUtils.swift rename to libs/python/lume/src/Utils/CommandUtils.swift diff --git a/libs/lume/src/Utils/Logger.swift b/libs/python/lume/src/Utils/Logger.swift similarity index 100% rename from libs/lume/src/Utils/Logger.swift rename to libs/python/lume/src/Utils/Logger.swift diff --git a/libs/lume/src/Utils/NetworkUtils.swift b/libs/python/lume/src/Utils/NetworkUtils.swift similarity index 100% rename from libs/lume/src/Utils/NetworkUtils.swift rename to libs/python/lume/src/Utils/NetworkUtils.swift diff --git a/libs/lume/src/Utils/Path.swift b/libs/python/lume/src/Utils/Path.swift similarity index 100% rename from libs/lume/src/Utils/Path.swift rename to libs/python/lume/src/Utils/Path.swift diff --git a/libs/lume/src/Utils/ProcessRunner.swift b/libs/python/lume/src/Utils/ProcessRunner.swift similarity index 100% rename from libs/lume/src/Utils/ProcessRunner.swift rename to libs/python/lume/src/Utils/ProcessRunner.swift diff --git a/libs/lume/src/Utils/ProgressLogger.swift b/libs/python/lume/src/Utils/ProgressLogger.swift similarity index 100% rename from libs/lume/src/Utils/ProgressLogger.swift rename to libs/python/lume/src/Utils/ProgressLogger.swift diff --git a/libs/lume/src/Utils/String.swift b/libs/python/lume/src/Utils/String.swift similarity index 100% rename from libs/lume/src/Utils/String.swift rename to libs/python/lume/src/Utils/String.swift diff --git a/libs/lume/src/Utils/Utils.swift b/libs/python/lume/src/Utils/Utils.swift similarity index 100% rename from libs/lume/src/Utils/Utils.swift rename to libs/python/lume/src/Utils/Utils.swift diff --git a/libs/lume/src/VM/DarwinVM.swift b/libs/python/lume/src/VM/DarwinVM.swift similarity index 100% rename from libs/lume/src/VM/DarwinVM.swift rename to libs/python/lume/src/VM/DarwinVM.swift diff --git a/libs/lume/src/VM/LinuxVM.swift b/libs/python/lume/src/VM/LinuxVM.swift similarity index 100% rename from libs/lume/src/VM/LinuxVM.swift rename to libs/python/lume/src/VM/LinuxVM.swift diff --git a/libs/lume/src/VM/VM.swift b/libs/python/lume/src/VM/VM.swift similarity index 100% rename from libs/lume/src/VM/VM.swift rename to libs/python/lume/src/VM/VM.swift diff --git a/libs/lume/src/VM/VMDetails.swift b/libs/python/lume/src/VM/VMDetails.swift similarity index 100% rename from libs/lume/src/VM/VMDetails.swift rename to libs/python/lume/src/VM/VMDetails.swift diff --git a/libs/lume/src/VM/VMDetailsPrinter.swift b/libs/python/lume/src/VM/VMDetailsPrinter.swift similarity index 100% rename from libs/lume/src/VM/VMDetailsPrinter.swift rename to libs/python/lume/src/VM/VMDetailsPrinter.swift diff --git a/libs/lume/src/VM/VMDisplayResolution.swift b/libs/python/lume/src/VM/VMDisplayResolution.swift similarity index 100% rename from libs/lume/src/VM/VMDisplayResolution.swift rename to libs/python/lume/src/VM/VMDisplayResolution.swift diff --git a/libs/lume/src/VM/VMFactory.swift b/libs/python/lume/src/VM/VMFactory.swift similarity index 100% rename from libs/lume/src/VM/VMFactory.swift rename to libs/python/lume/src/VM/VMFactory.swift diff --git a/libs/lume/src/VNC/PassphraseGenerator.swift b/libs/python/lume/src/VNC/PassphraseGenerator.swift similarity index 100% rename from libs/lume/src/VNC/PassphraseGenerator.swift rename to libs/python/lume/src/VNC/PassphraseGenerator.swift diff --git a/libs/lume/src/VNC/VNCService.swift b/libs/python/lume/src/VNC/VNCService.swift similarity index 100% rename from libs/lume/src/VNC/VNCService.swift rename to libs/python/lume/src/VNC/VNCService.swift diff --git a/libs/lume/src/Virtualization/DHCPLeaseParser.swift b/libs/python/lume/src/Virtualization/DHCPLeaseParser.swift similarity index 100% rename from libs/lume/src/Virtualization/DHCPLeaseParser.swift rename to libs/python/lume/src/Virtualization/DHCPLeaseParser.swift diff --git a/libs/lume/src/Virtualization/DarwinImageLoader.swift b/libs/python/lume/src/Virtualization/DarwinImageLoader.swift similarity index 100% rename from libs/lume/src/Virtualization/DarwinImageLoader.swift rename to libs/python/lume/src/Virtualization/DarwinImageLoader.swift diff --git a/libs/lume/src/Virtualization/ImageLoaderFactory.swift b/libs/python/lume/src/Virtualization/ImageLoaderFactory.swift similarity index 100% rename from libs/lume/src/Virtualization/ImageLoaderFactory.swift rename to libs/python/lume/src/Virtualization/ImageLoaderFactory.swift diff --git a/libs/lume/src/Virtualization/VMVirtualizationService.swift b/libs/python/lume/src/Virtualization/VMVirtualizationService.swift similarity index 100% rename from libs/lume/src/Virtualization/VMVirtualizationService.swift rename to libs/python/lume/src/Virtualization/VMVirtualizationService.swift diff --git a/libs/lume/tests/Mocks/MockVM.swift b/libs/python/lume/tests/Mocks/MockVM.swift similarity index 100% rename from libs/lume/tests/Mocks/MockVM.swift rename to libs/python/lume/tests/Mocks/MockVM.swift diff --git a/libs/lume/tests/Mocks/MockVMVirtualizationService.swift b/libs/python/lume/tests/Mocks/MockVMVirtualizationService.swift similarity index 100% rename from libs/lume/tests/Mocks/MockVMVirtualizationService.swift rename to libs/python/lume/tests/Mocks/MockVMVirtualizationService.swift diff --git a/libs/lume/tests/Mocks/MockVNCService.swift b/libs/python/lume/tests/Mocks/MockVNCService.swift similarity index 100% rename from libs/lume/tests/Mocks/MockVNCService.swift rename to libs/python/lume/tests/Mocks/MockVNCService.swift diff --git a/libs/lume/tests/VM/VMDetailsPrinterTests.swift b/libs/python/lume/tests/VM/VMDetailsPrinterTests.swift similarity index 100% rename from libs/lume/tests/VM/VMDetailsPrinterTests.swift rename to libs/python/lume/tests/VM/VMDetailsPrinterTests.swift diff --git a/libs/lume/tests/VMTests.swift b/libs/python/lume/tests/VMTests.swift similarity index 100% rename from libs/lume/tests/VMTests.swift rename to libs/python/lume/tests/VMTests.swift diff --git a/libs/lume/tests/VMVirtualizationServiceTests.swift b/libs/python/lume/tests/VMVirtualizationServiceTests.swift similarity index 100% rename from libs/lume/tests/VMVirtualizationServiceTests.swift rename to libs/python/lume/tests/VMVirtualizationServiceTests.swift diff --git a/libs/lume/tests/VNCServiceTests.swift b/libs/python/lume/tests/VNCServiceTests.swift similarity index 100% rename from libs/lume/tests/VNCServiceTests.swift rename to libs/python/lume/tests/VNCServiceTests.swift diff --git a/libs/lumier/.dockerignore b/libs/python/lumier/.dockerignore similarity index 100% rename from libs/lumier/.dockerignore rename to libs/python/lumier/.dockerignore diff --git a/libs/lumier/Dockerfile b/libs/python/lumier/Dockerfile similarity index 100% rename from libs/lumier/Dockerfile rename to libs/python/lumier/Dockerfile diff --git a/libs/lumier/README.md b/libs/python/lumier/README.md similarity index 100% rename from libs/lumier/README.md rename to libs/python/lumier/README.md diff --git a/libs/lumier/src/bin/entry.sh b/libs/python/lumier/src/bin/entry.sh similarity index 100% rename from libs/lumier/src/bin/entry.sh rename to libs/python/lumier/src/bin/entry.sh diff --git a/libs/lumier/src/config/constants.sh b/libs/python/lumier/src/config/constants.sh similarity index 100% rename from libs/lumier/src/config/constants.sh rename to libs/python/lumier/src/config/constants.sh diff --git a/libs/lumier/src/hooks/on-logon.sh b/libs/python/lumier/src/hooks/on-logon.sh similarity index 100% rename from libs/lumier/src/hooks/on-logon.sh rename to libs/python/lumier/src/hooks/on-logon.sh diff --git a/libs/lumier/src/lib/utils.sh b/libs/python/lumier/src/lib/utils.sh similarity index 100% rename from libs/lumier/src/lib/utils.sh rename to libs/python/lumier/src/lib/utils.sh diff --git a/libs/lumier/src/lib/vm.sh b/libs/python/lumier/src/lib/vm.sh similarity index 100% rename from libs/lumier/src/lib/vm.sh rename to libs/python/lumier/src/lib/vm.sh diff --git a/libs/mcp-server/README.md b/libs/python/mcp-server/README.md similarity index 100% rename from libs/mcp-server/README.md rename to libs/python/mcp-server/README.md diff --git a/libs/mcp-server/mcp_server/__init__.py b/libs/python/mcp-server/mcp_server/__init__.py similarity index 100% rename from libs/mcp-server/mcp_server/__init__.py rename to libs/python/mcp-server/mcp_server/__init__.py diff --git a/libs/mcp-server/mcp_server/__main__.py b/libs/python/mcp-server/mcp_server/__main__.py similarity index 100% rename from libs/mcp-server/mcp_server/__main__.py rename to libs/python/mcp-server/mcp_server/__main__.py diff --git a/libs/mcp-server/mcp_server/server.py b/libs/python/mcp-server/mcp_server/server.py similarity index 100% rename from libs/mcp-server/mcp_server/server.py rename to libs/python/mcp-server/mcp_server/server.py diff --git a/libs/mcp-server/pyproject.toml b/libs/python/mcp-server/pyproject.toml similarity index 100% rename from libs/mcp-server/pyproject.toml rename to libs/python/mcp-server/pyproject.toml diff --git a/libs/mcp-server/scripts/install_mcp_server.sh b/libs/python/mcp-server/scripts/install_mcp_server.sh similarity index 100% rename from libs/mcp-server/scripts/install_mcp_server.sh rename to libs/python/mcp-server/scripts/install_mcp_server.sh diff --git a/libs/mcp-server/scripts/start_mcp_server.sh b/libs/python/mcp-server/scripts/start_mcp_server.sh similarity index 100% rename from libs/mcp-server/scripts/start_mcp_server.sh rename to libs/python/mcp-server/scripts/start_mcp_server.sh diff --git a/libs/pylume/README.md b/libs/python/pylume/README.md similarity index 100% rename from libs/pylume/README.md rename to libs/python/pylume/README.md diff --git a/libs/pylume/__init__.py b/libs/python/pylume/__init__.py similarity index 100% rename from libs/pylume/__init__.py rename to libs/python/pylume/__init__.py diff --git a/libs/pylume/pylume/__init__.py b/libs/python/pylume/pylume/__init__.py similarity index 100% rename from libs/pylume/pylume/__init__.py rename to libs/python/pylume/pylume/__init__.py diff --git a/libs/pylume/pylume/client.py b/libs/python/pylume/pylume/client.py similarity index 100% rename from libs/pylume/pylume/client.py rename to libs/python/pylume/pylume/client.py diff --git a/libs/pylume/pylume/exceptions.py b/libs/python/pylume/pylume/exceptions.py similarity index 100% rename from libs/pylume/pylume/exceptions.py rename to libs/python/pylume/pylume/exceptions.py diff --git a/libs/pylume/pylume/lume b/libs/python/pylume/pylume/lume similarity index 100% rename from libs/pylume/pylume/lume rename to libs/python/pylume/pylume/lume diff --git a/libs/pylume/pylume/models.py b/libs/python/pylume/pylume/models.py similarity index 100% rename from libs/pylume/pylume/models.py rename to libs/python/pylume/pylume/models.py diff --git a/libs/pylume/pylume/pylume.py b/libs/python/pylume/pylume/pylume.py similarity index 100% rename from libs/pylume/pylume/pylume.py rename to libs/python/pylume/pylume/pylume.py diff --git a/libs/pylume/pylume/server.py b/libs/python/pylume/pylume/server.py similarity index 100% rename from libs/pylume/pylume/server.py rename to libs/python/pylume/pylume/server.py diff --git a/libs/pylume/pyproject.toml b/libs/python/pylume/pyproject.toml similarity index 100% rename from libs/pylume/pyproject.toml rename to libs/python/pylume/pyproject.toml diff --git a/libs/som/README.md b/libs/python/som/README.md similarity index 100% rename from libs/som/README.md rename to libs/python/som/README.md diff --git a/libs/som/poetry.toml b/libs/python/som/poetry.toml similarity index 100% rename from libs/som/poetry.toml rename to libs/python/som/poetry.toml diff --git a/libs/som/pyproject.toml b/libs/python/som/pyproject.toml similarity index 100% rename from libs/som/pyproject.toml rename to libs/python/som/pyproject.toml diff --git a/libs/som/som/__init__.py b/libs/python/som/som/__init__.py similarity index 100% rename from libs/som/som/__init__.py rename to libs/python/som/som/__init__.py diff --git a/libs/som/som/detect.py b/libs/python/som/som/detect.py similarity index 100% rename from libs/som/som/detect.py rename to libs/python/som/som/detect.py diff --git a/libs/som/som/detection.py b/libs/python/som/som/detection.py similarity index 100% rename from libs/som/som/detection.py rename to libs/python/som/som/detection.py diff --git a/libs/som/som/models.py b/libs/python/som/som/models.py similarity index 100% rename from libs/som/som/models.py rename to libs/python/som/som/models.py diff --git a/libs/som/som/ocr.py b/libs/python/som/som/ocr.py similarity index 100% rename from libs/som/som/ocr.py rename to libs/python/som/som/ocr.py diff --git a/libs/som/som/util/utils.py b/libs/python/som/som/util/utils.py similarity index 100% rename from libs/som/som/util/utils.py rename to libs/python/som/som/util/utils.py diff --git a/libs/som/som/visualization.py b/libs/python/som/som/visualization.py similarity index 100% rename from libs/som/som/visualization.py rename to libs/python/som/som/visualization.py diff --git a/libs/som/tests/test_omniparser.py b/libs/python/som/tests/test_omniparser.py similarity index 100% rename from libs/som/tests/test_omniparser.py rename to libs/python/som/tests/test_omniparser.py diff --git a/libs/computer/typescript/.editorconfig b/libs/typescript/computer/.editorconfig similarity index 100% rename from libs/computer/typescript/.editorconfig rename to libs/typescript/computer/.editorconfig diff --git a/libs/computer/typescript/.gitattributes b/libs/typescript/computer/.gitattributes similarity index 100% rename from libs/computer/typescript/.gitattributes rename to libs/typescript/computer/.gitattributes diff --git a/libs/computer/typescript/.github/_workflows/release.yml b/libs/typescript/computer/.github/_workflows/release.yml similarity index 100% rename from libs/computer/typescript/.github/_workflows/release.yml rename to libs/typescript/computer/.github/_workflows/release.yml diff --git a/libs/computer/typescript/.github/_workflows/unit-test.yml b/libs/typescript/computer/.github/_workflows/unit-test.yml similarity index 100% rename from libs/computer/typescript/.github/_workflows/unit-test.yml rename to libs/typescript/computer/.github/_workflows/unit-test.yml diff --git a/libs/computer/typescript/.gitignore b/libs/typescript/computer/.gitignore similarity index 100% rename from libs/computer/typescript/.gitignore rename to libs/typescript/computer/.gitignore diff --git a/libs/computer/typescript/.nvmrc b/libs/typescript/computer/.nvmrc similarity index 100% rename from libs/computer/typescript/.nvmrc rename to libs/typescript/computer/.nvmrc diff --git a/libs/computer/typescript/LICENSE b/libs/typescript/computer/LICENSE similarity index 100% rename from libs/computer/typescript/LICENSE rename to libs/typescript/computer/LICENSE diff --git a/libs/computer/typescript/README.md b/libs/typescript/computer/README.md similarity index 100% rename from libs/computer/typescript/README.md rename to libs/typescript/computer/README.md diff --git a/libs/computer/typescript/biome.json b/libs/typescript/computer/biome.json similarity index 100% rename from libs/computer/typescript/biome.json rename to libs/typescript/computer/biome.json diff --git a/libs/computer/typescript/package.json b/libs/typescript/computer/package.json similarity index 100% rename from libs/computer/typescript/package.json rename to libs/typescript/computer/package.json diff --git a/libs/computer/typescript/pnpm-lock.yaml b/libs/typescript/computer/pnpm-lock.yaml similarity index 100% rename from libs/computer/typescript/pnpm-lock.yaml rename to libs/typescript/computer/pnpm-lock.yaml diff --git a/libs/computer/typescript/pnpm-workspace.yaml b/libs/typescript/computer/pnpm-workspace.yaml similarity index 100% rename from libs/computer/typescript/pnpm-workspace.yaml rename to libs/typescript/computer/pnpm-workspace.yaml diff --git a/libs/computer/typescript/src/computer/index.ts b/libs/typescript/computer/src/computer/index.ts similarity index 100% rename from libs/computer/typescript/src/computer/index.ts rename to libs/typescript/computer/src/computer/index.ts diff --git a/libs/computer/typescript/src/computer/providers/base.ts b/libs/typescript/computer/src/computer/providers/base.ts similarity index 100% rename from libs/computer/typescript/src/computer/providers/base.ts rename to libs/typescript/computer/src/computer/providers/base.ts diff --git a/libs/computer/typescript/src/computer/providers/cloud.ts b/libs/typescript/computer/src/computer/providers/cloud.ts similarity index 100% rename from libs/computer/typescript/src/computer/providers/cloud.ts rename to libs/typescript/computer/src/computer/providers/cloud.ts diff --git a/libs/computer/typescript/src/computer/providers/index.ts b/libs/typescript/computer/src/computer/providers/index.ts similarity index 100% rename from libs/computer/typescript/src/computer/providers/index.ts rename to libs/typescript/computer/src/computer/providers/index.ts diff --git a/libs/computer/typescript/src/computer/types.ts b/libs/typescript/computer/src/computer/types.ts similarity index 100% rename from libs/computer/typescript/src/computer/types.ts rename to libs/typescript/computer/src/computer/types.ts diff --git a/libs/computer/typescript/src/index.ts b/libs/typescript/computer/src/index.ts similarity index 100% rename from libs/computer/typescript/src/index.ts rename to libs/typescript/computer/src/index.ts diff --git a/libs/computer/typescript/src/interface/base.ts b/libs/typescript/computer/src/interface/base.ts similarity index 100% rename from libs/computer/typescript/src/interface/base.ts rename to libs/typescript/computer/src/interface/base.ts diff --git a/libs/computer/typescript/src/interface/factory.ts b/libs/typescript/computer/src/interface/factory.ts similarity index 100% rename from libs/computer/typescript/src/interface/factory.ts rename to libs/typescript/computer/src/interface/factory.ts diff --git a/libs/computer/typescript/src/interface/index.ts b/libs/typescript/computer/src/interface/index.ts similarity index 100% rename from libs/computer/typescript/src/interface/index.ts rename to libs/typescript/computer/src/interface/index.ts diff --git a/libs/computer/typescript/src/interface/linux.ts b/libs/typescript/computer/src/interface/linux.ts similarity index 100% rename from libs/computer/typescript/src/interface/linux.ts rename to libs/typescript/computer/src/interface/linux.ts diff --git a/libs/computer/typescript/src/interface/macos.ts b/libs/typescript/computer/src/interface/macos.ts similarity index 100% rename from libs/computer/typescript/src/interface/macos.ts rename to libs/typescript/computer/src/interface/macos.ts diff --git a/libs/computer/typescript/src/interface/windows.ts b/libs/typescript/computer/src/interface/windows.ts similarity index 100% rename from libs/computer/typescript/src/interface/windows.ts rename to libs/typescript/computer/src/interface/windows.ts diff --git a/libs/computer/typescript/src/types.ts b/libs/typescript/computer/src/types.ts similarity index 100% rename from libs/computer/typescript/src/types.ts rename to libs/typescript/computer/src/types.ts diff --git a/libs/computer/typescript/tests/computer/cloud.test.ts b/libs/typescript/computer/tests/computer/cloud.test.ts similarity index 100% rename from libs/computer/typescript/tests/computer/cloud.test.ts rename to libs/typescript/computer/tests/computer/cloud.test.ts diff --git a/libs/computer/typescript/tests/interface/factory.test.ts b/libs/typescript/computer/tests/interface/factory.test.ts similarity index 100% rename from libs/computer/typescript/tests/interface/factory.test.ts rename to libs/typescript/computer/tests/interface/factory.test.ts diff --git a/libs/computer/typescript/tests/interface/index.test.ts b/libs/typescript/computer/tests/interface/index.test.ts similarity index 100% rename from libs/computer/typescript/tests/interface/index.test.ts rename to libs/typescript/computer/tests/interface/index.test.ts diff --git a/libs/computer/typescript/tests/interface/linux.test.ts b/libs/typescript/computer/tests/interface/linux.test.ts similarity index 100% rename from libs/computer/typescript/tests/interface/linux.test.ts rename to libs/typescript/computer/tests/interface/linux.test.ts diff --git a/libs/computer/typescript/tests/interface/macos.test.ts b/libs/typescript/computer/tests/interface/macos.test.ts similarity index 100% rename from libs/computer/typescript/tests/interface/macos.test.ts rename to libs/typescript/computer/tests/interface/macos.test.ts diff --git a/libs/computer/typescript/tests/interface/windows.test.ts b/libs/typescript/computer/tests/interface/windows.test.ts similarity index 100% rename from libs/computer/typescript/tests/interface/windows.test.ts rename to libs/typescript/computer/tests/interface/windows.test.ts diff --git a/libs/computer/typescript/tests/setup.ts b/libs/typescript/computer/tests/setup.ts similarity index 100% rename from libs/computer/typescript/tests/setup.ts rename to libs/typescript/computer/tests/setup.ts diff --git a/libs/computer/typescript/tsconfig.json b/libs/typescript/computer/tsconfig.json similarity index 100% rename from libs/computer/typescript/tsconfig.json rename to libs/typescript/computer/tsconfig.json diff --git a/libs/computer/typescript/tsdown.config.ts b/libs/typescript/computer/tsdown.config.ts similarity index 100% rename from libs/computer/typescript/tsdown.config.ts rename to libs/typescript/computer/tsdown.config.ts diff --git a/libs/computer/typescript/vitest.config.ts b/libs/typescript/computer/vitest.config.ts similarity index 100% rename from libs/computer/typescript/vitest.config.ts rename to libs/typescript/computer/vitest.config.ts diff --git a/libs/typescript/core/.editorconfig b/libs/typescript/core/.editorconfig new file mode 100644 index 00000000..7095e7fb --- /dev/null +++ b/libs/typescript/core/.editorconfig @@ -0,0 +1,6 @@ +root = true + +[*] +indent_size = 2 +end_of_line = lf +insert_final_newline = true diff --git a/libs/typescript/core/.gitattributes b/libs/typescript/core/.gitattributes new file mode 100644 index 00000000..6313b56c --- /dev/null +++ b/libs/typescript/core/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/libs/typescript/core/.github/_workflows/release.yml b/libs/typescript/core/.github/_workflows/release.yml new file mode 100644 index 00000000..e5b7a4c3 --- /dev/null +++ b/libs/typescript/core/.github/_workflows/release.yml @@ -0,0 +1,26 @@ +name: Release + +permissions: + contents: write + +on: + push: + tags: + - 'v*' + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set node + uses: actions/setup-node@v4 + with: + node-version: lts/* + + - run: npx changelogithub + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/libs/typescript/core/.github/_workflows/unit-test.yml b/libs/typescript/core/.github/_workflows/unit-test.yml new file mode 100644 index 00000000..dc3418c5 --- /dev/null +++ b/libs/typescript/core/.github/_workflows/unit-test.yml @@ -0,0 +1,38 @@ +name: Unit Test + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4.1.0 + + - name: Set node LTS + uses: actions/setup-node@v4 + with: + node-version: lts/* + cache: pnpm + + - name: Install + run: pnpm install + + - name: Build + run: pnpm run build + + - name: Lint + run: pnpm run lint + + - name: Typecheck + run: pnpm run typecheck + + - name: Test + run: pnpm run test diff --git a/libs/typescript/core/.gitignore b/libs/typescript/core/.gitignore new file mode 100644 index 00000000..e79f2036 --- /dev/null +++ b/libs/typescript/core/.gitignore @@ -0,0 +1,6 @@ +node_modules +dist + +*.log +.DS_Store +.eslintcache diff --git a/libs/typescript/core/.vscode/settings.json b/libs/typescript/core/.vscode/settings.json new file mode 100644 index 00000000..ad92582b --- /dev/null +++ b/libs/typescript/core/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.formatOnSave": true +} diff --git a/libs/typescript/core/LICENSE b/libs/typescript/core/LICENSE new file mode 100644 index 00000000..74987166 --- /dev/null +++ b/libs/typescript/core/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright © 2025 C/ua + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/libs/typescript/core/README.md b/libs/typescript/core/README.md new file mode 100644 index 00000000..a6b568ff --- /dev/null +++ b/libs/typescript/core/README.md @@ -0,0 +1,27 @@ +# tsdown-starter + +A starter for creating a tsdown package. + +## Development + +- Install dependencies: + +```bash +pnpm install +``` + +- Run the unit tests: + +```bash +pnpm test +``` + +- Build the library: + +```bash +pnpm build +``` + +## License + +[MIT](./LICENSE) License 2025 [C/UA](https://github.com/trycua) diff --git a/libs/typescript/core/biome.json b/libs/typescript/core/biome.json new file mode 100644 index 00000000..3251a52d --- /dev/null +++ b/libs/typescript/core/biome.json @@ -0,0 +1,86 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "ignore": [ + "dist", + "node_modules" + ] + }, + "formatter": { + "enabled": true, + "useEditorconfig": true, + "formatWithErrors": false, + "indentStyle": "space", + "indentWidth": 2, + "lineEnding": "lf", + "lineWidth": 80, + "attributePosition": "auto", + "bracketSpacing": true + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "style": { + "useSelfClosingElements": "warn", + "noUnusedTemplateLiteral": "warn", + "noNonNullAssertion": "off" + }, + "a11y": { + "useMediaCaption": "off", + "useKeyWithClickEvents": "warn", + "useKeyWithMouseEvents": "warn", + "noSvgWithoutTitle": "off", + "useButtonType": "warn", + "noAutofocus": "off" + }, + "suspicious": { + "noArrayIndexKey": "off" + }, + "correctness": { + "noUnusedVariables": "warn", + "noUnusedFunctionParameters": "warn", + "noUnusedImports": "warn" + }, + "complexity": { + "useOptionalChain": "info" + }, + "nursery": { + "useSortedClasses": { + "level": "warn", + "fix": "safe", + "options": { + "attributes": [ + "className" + ], + "functions": [ + "cn" + ] + } + } + } + } + }, + "javascript": { + "formatter": { + "jsxQuoteStyle": "double", + "quoteProperties": "asNeeded", + "trailingCommas": "es5", + "semicolons": "always", + "arrowParentheses": "always", + "bracketSameLine": false, + "quoteStyle": "single", + "attributePosition": "auto", + "bracketSpacing": true + } + } +} \ No newline at end of file diff --git a/libs/typescript/core/package.json b/libs/typescript/core/package.json new file mode 100644 index 00000000..f9b0109c --- /dev/null +++ b/libs/typescript/core/package.json @@ -0,0 +1,54 @@ +{ + "name": "@cua/core", + "version": "0.0.1", + "packageManager": "pnpm@10.11.0", + "description": "Typescript SDK for c/ua core", + "type": "module", + "license": "MIT", + "homepage": "", + "bugs": { + "url": "https://github.com/trycua/cua/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/trycua/cua.git" + }, + "author": "c/ua", + "files": [ + "dist" + ], + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": "./dist/index.js", + "./package.json": "./package.json" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "lint": "biome lint .", + "lint:fix": "biome lint --fix .", + "build": "tsdown", + "dev": "tsdown --watch", + "test": "vitest", + "typecheck": "tsc --noEmit", + "release": "bumpp && pnpm publish", + "prepublishOnly": "pnpm run build" + }, + "dependencies": { + "pino": "^9.7.0" + }, + "devDependencies": { + "@biomejs/biome": "^1.9.4", + "@types/node": "^22.15.17", + "@types/ws": "^8.18.1", + "bumpp": "^10.1.0", + "happy-dom": "^17.4.7", + "tsdown": "^0.11.9", + "tsx": "^4.19.4", + "typescript": "^5.8.3", + "vitest": "^3.1.3" + } +} \ No newline at end of file diff --git a/libs/typescript/core/pnpm-lock.yaml b/libs/typescript/core/pnpm-lock.yaml new file mode 100644 index 00000000..2f35a784 --- /dev/null +++ b/libs/typescript/core/pnpm-lock.yaml @@ -0,0 +1,5724 @@ +lockfileVersion: "9.0" + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + .: + devDependencies: + "@sxzz/eslint-config": + specifier: ^7.0.1 + version: 7.0.4(@typescript-eslint/eslint-plugin@8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(@typescript-eslint/utils@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + "@sxzz/prettier-config": + specifier: ^2.2.1 + version: 2.2.1 + "@types/node": + specifier: ^22.15.17 + version: 22.15.32 + bumpp: + specifier: ^10.1.0 + version: 10.2.0 + eslint: + specifier: ^9.26.0 + version: 9.29.0(jiti@2.4.2) + happy-dom: + specifier: ^17.4.7 + version: 17.6.3 + prettier: + specifier: ^3.5.3 + version: 3.6.0 + tsdown: + specifier: ^0.11.9 + version: 0.11.13(typescript@5.8.3) + tsx: + specifier: ^4.19.4 + version: 4.20.3 + typescript: + specifier: ^5.8.3 + version: 5.8.3 + vitest: + specifier: ^3.1.3 + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.32)(happy-dom@17.6.3)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + +packages: + "@babel/generator@7.27.5": + resolution: + { + integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-string-parser@7.27.1": + resolution: + { + integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==, + } + engines: { node: ">=6.9.0" } + + "@babel/helper-validator-identifier@7.27.1": + resolution: + { + integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==, + } + engines: { node: ">=6.9.0" } + + "@babel/parser@7.27.5": + resolution: + { + integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==, + } + engines: { node: ">=6.0.0" } + hasBin: true + + "@babel/types@7.27.6": + resolution: + { + integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==, + } + engines: { node: ">=6.9.0" } + + "@emnapi/core@1.4.3": + resolution: + { + integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==, + } + + "@emnapi/runtime@1.4.3": + resolution: + { + integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==, + } + + "@emnapi/wasi-threads@1.0.2": + resolution: + { + integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==, + } + + "@es-joy/jsdoccomment@0.50.2": + resolution: + { + integrity: sha512-YAdE/IJSpwbOTiaURNCKECdAwqrJuFiZhylmesBcIRawtYKnBR2wxPhoIewMg+Yu+QuYvHfJNReWpoxGBKOChA==, + } + engines: { node: ">=18" } + + "@es-joy/jsdoccomment@0.52.0": + resolution: + { + integrity: sha512-BXuN7BII+8AyNtn57euU2Yxo9yA/KUDNzrpXyi3pfqKmBhhysR6ZWOebFh3vyPoqA3/j1SOvGgucElMGwlXing==, + } + engines: { node: ">=20.11.0" } + + "@esbuild/aix-ppc64@0.25.5": + resolution: + { + integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==, + } + engines: { node: ">=18" } + cpu: [ppc64] + os: [aix] + + "@esbuild/android-arm64@0.25.5": + resolution: + { + integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [android] + + "@esbuild/android-arm@0.25.5": + resolution: + { + integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==, + } + engines: { node: ">=18" } + cpu: [arm] + os: [android] + + "@esbuild/android-x64@0.25.5": + resolution: + { + integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [android] + + "@esbuild/darwin-arm64@0.25.5": + resolution: + { + integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [darwin] + + "@esbuild/darwin-x64@0.25.5": + resolution: + { + integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [darwin] + + "@esbuild/freebsd-arm64@0.25.5": + resolution: + { + integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [freebsd] + + "@esbuild/freebsd-x64@0.25.5": + resolution: + { + integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [freebsd] + + "@esbuild/linux-arm64@0.25.5": + resolution: + { + integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [linux] + + "@esbuild/linux-arm@0.25.5": + resolution: + { + integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==, + } + engines: { node: ">=18" } + cpu: [arm] + os: [linux] + + "@esbuild/linux-ia32@0.25.5": + resolution: + { + integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==, + } + engines: { node: ">=18" } + cpu: [ia32] + os: [linux] + + "@esbuild/linux-loong64@0.25.5": + resolution: + { + integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==, + } + engines: { node: ">=18" } + cpu: [loong64] + os: [linux] + + "@esbuild/linux-mips64el@0.25.5": + resolution: + { + integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==, + } + engines: { node: ">=18" } + cpu: [mips64el] + os: [linux] + + "@esbuild/linux-ppc64@0.25.5": + resolution: + { + integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==, + } + engines: { node: ">=18" } + cpu: [ppc64] + os: [linux] + + "@esbuild/linux-riscv64@0.25.5": + resolution: + { + integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==, + } + engines: { node: ">=18" } + cpu: [riscv64] + os: [linux] + + "@esbuild/linux-s390x@0.25.5": + resolution: + { + integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==, + } + engines: { node: ">=18" } + cpu: [s390x] + os: [linux] + + "@esbuild/linux-x64@0.25.5": + resolution: + { + integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [linux] + + "@esbuild/netbsd-arm64@0.25.5": + resolution: + { + integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [netbsd] + + "@esbuild/netbsd-x64@0.25.5": + resolution: + { + integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [netbsd] + + "@esbuild/openbsd-arm64@0.25.5": + resolution: + { + integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [openbsd] + + "@esbuild/openbsd-x64@0.25.5": + resolution: + { + integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [openbsd] + + "@esbuild/sunos-x64@0.25.5": + resolution: + { + integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [sunos] + + "@esbuild/win32-arm64@0.25.5": + resolution: + { + integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==, + } + engines: { node: ">=18" } + cpu: [arm64] + os: [win32] + + "@esbuild/win32-ia32@0.25.5": + resolution: + { + integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==, + } + engines: { node: ">=18" } + cpu: [ia32] + os: [win32] + + "@esbuild/win32-x64@0.25.5": + resolution: + { + integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==, + } + engines: { node: ">=18" } + cpu: [x64] + os: [win32] + + "@eslint-community/eslint-plugin-eslint-comments@4.5.0": + resolution: + { + integrity: sha512-MAhuTKlr4y/CE3WYX26raZjy+I/kS2PLKSzvfmDCGrBLTFHOYwqROZdr4XwPgXwX3K9rjzMr4pSmUWGnzsUyMg==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + + "@eslint-community/eslint-utils@4.7.0": + resolution: + { + integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + "@eslint-community/regexpp@4.12.1": + resolution: + { + integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==, + } + engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } + + "@eslint/compat@1.3.0": + resolution: + { + integrity: sha512-ZBygRBqpDYiIHsN+d1WyHn3TYgzgpzLEcgJUxTATyiInQbKZz6wZb6+ljwdg8xeeOe4v03z6Uh6lELiw0/mVhQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^9.10.0 + peerDependenciesMeta: + eslint: + optional: true + + "@eslint/config-array@0.20.1": + resolution: + { + integrity: sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/config-helpers@0.2.3": + resolution: + { + integrity: sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/core@0.13.0": + resolution: + { + integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/core@0.14.0": + resolution: + { + integrity: sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/core@0.15.0": + resolution: + { + integrity: sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/eslintrc@3.3.1": + resolution: + { + integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/js@9.29.0": + resolution: + { + integrity: sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/markdown@6.6.0": + resolution: + { + integrity: sha512-IsWPy2jU3gaQDlioDC4sT4I4kG1hX1OMWs/q2sWwJrPoMASHW/Z4SDw+6Aql6EsHejGbagYuJbFq9Zvx+Y1b1Q==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/object-schema@2.1.6": + resolution: + { + integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/plugin-kit@0.2.8": + resolution: + { + integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@eslint/plugin-kit@0.3.2": + resolution: + { + integrity: sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@humanfs/core@0.19.1": + resolution: + { + integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==, + } + engines: { node: ">=18.18.0" } + + "@humanfs/node@0.16.6": + resolution: + { + integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==, + } + engines: { node: ">=18.18.0" } + + "@humanwhocodes/module-importer@1.0.1": + resolution: + { + integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==, + } + engines: { node: ">=12.22" } + + "@humanwhocodes/retry@0.3.1": + resolution: + { + integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==, + } + engines: { node: ">=18.18" } + + "@humanwhocodes/retry@0.4.3": + resolution: + { + integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==, + } + engines: { node: ">=18.18" } + + "@isaacs/balanced-match@4.0.1": + resolution: + { + integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==, + } + engines: { node: 20 || >=22 } + + "@isaacs/brace-expansion@5.0.0": + resolution: + { + integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==, + } + engines: { node: 20 || >=22 } + + "@jridgewell/gen-mapping@0.3.8": + resolution: + { + integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==, + } + engines: { node: ">=6.0.0" } + + "@jridgewell/resolve-uri@3.1.2": + resolution: + { + integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==, + } + engines: { node: ">=6.0.0" } + + "@jridgewell/set-array@1.2.1": + resolution: + { + integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==, + } + engines: { node: ">=6.0.0" } + + "@jridgewell/sourcemap-codec@1.5.0": + resolution: + { + integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==, + } + + "@jridgewell/trace-mapping@0.3.25": + resolution: + { + integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==, + } + + "@napi-rs/wasm-runtime@0.2.11": + resolution: + { + integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==, + } + + "@nodelib/fs.scandir@2.1.5": + resolution: + { + integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==, + } + engines: { node: ">= 8" } + + "@nodelib/fs.stat@2.0.5": + resolution: + { + integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==, + } + engines: { node: ">= 8" } + + "@nodelib/fs.walk@1.2.8": + resolution: + { + integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==, + } + engines: { node: ">= 8" } + + "@oxc-project/types@0.70.0": + resolution: + { + integrity: sha512-ngyLUpUjO3dpqygSRQDx7nMx8+BmXbWOU4oIwTJFV2MVIDG7knIZwgdwXlQWLg3C3oxg1lS7ppMtPKqKFb7wzw==, + } + + "@pkgr/core@0.2.7": + resolution: + { + integrity: sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==, + } + engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } + + "@quansync/fs@0.1.3": + resolution: + { + integrity: sha512-G0OnZbMWEs5LhDyqy2UL17vGhSVHkQIfVojMtEWVenvj0V5S84VBgy86kJIuNsGDp2p7sTKlpSIpBUWdC35OKg==, + } + engines: { node: ">=20.0.0" } + + "@rolldown/binding-darwin-arm64@1.0.0-beta.9": + resolution: + { + integrity: sha512-geUG/FUpm+membLC0NQBb39vVyOfguYZ2oyXc7emr6UjH6TeEECT4b0CPZXKFnELareTiU/Jfl70/eEgNxyQeA==, + } + cpu: [arm64] + os: [darwin] + + "@rolldown/binding-darwin-x64@1.0.0-beta.9": + resolution: + { + integrity: sha512-7wPXDwcOtv2I+pWTL2UNpNAxMAGukgBT90Jz4DCfwaYdGvQncF7J0S7IWrRVsRFhBavxM+65RcueE3VXw5UIbg==, + } + cpu: [x64] + os: [darwin] + + "@rolldown/binding-freebsd-x64@1.0.0-beta.9": + resolution: + { + integrity: sha512-agO5mONTNKVrcIt4SRxw5Ni0FOVV3gaH8dIiNp1A4JeU91b9kw7x+JRuNJAQuM2X3pYqVvA6qh13UTNOsaqM/Q==, + } + cpu: [x64] + os: [freebsd] + + "@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9": + resolution: + { + integrity: sha512-dDNDV9p/8WYDriS9HCcbH6y6+JP38o3enj/pMkdkmkxEnZ0ZoHIfQ9RGYWeRYU56NKBCrya4qZBJx49Jk9LRug==, + } + cpu: [arm] + os: [linux] + + "@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9": + resolution: + { + integrity: sha512-kZKegmHG1ZvfsFIwYU6DeFSxSIcIliXzeznsJHUo9D9/dlVSDi/PUvsRKcuJkQjZoejM6pk8MHN/UfgGdIhPHw==, + } + cpu: [arm64] + os: [linux] + + "@rolldown/binding-linux-arm64-musl@1.0.0-beta.9": + resolution: + { + integrity: sha512-f+VL8mO31pyMJiJPr2aA1ryYONkP2UqgbwK7fKtKHZIeDd/AoUGn3+ujPqDhuy2NxgcJ5H8NaSvDpG1tJMHh+g==, + } + cpu: [arm64] + os: [linux] + + "@rolldown/binding-linux-x64-gnu@1.0.0-beta.9": + resolution: + { + integrity: sha512-GiUEZ0WPjX5LouDoC3O8aJa4h6BLCpIvaAboNw5JoRour/3dC6rbtZZ/B5FC3/ySsN3/dFOhAH97ylQxoZJi7A==, + } + cpu: [x64] + os: [linux] + + "@rolldown/binding-linux-x64-musl@1.0.0-beta.9": + resolution: + { + integrity: sha512-AMb0dicw+QHh6RxvWo4BRcuTMgS0cwUejJRMpSyIcHYnKTbj6nUW4HbWNQuDfZiF27l6F5gEwBS+YLUdVzL9vg==, + } + cpu: [x64] + os: [linux] + + "@rolldown/binding-wasm32-wasi@1.0.0-beta.9": + resolution: + { + integrity: sha512-+pdaiTx7L8bWKvsAuCE0HAxP1ze1WOLoWGCawcrZbMSY10dMh2i82lJiH6tXGXbfYYwsNWhWE2NyG4peFZvRfQ==, + } + engines: { node: ">=14.21.3" } + cpu: [wasm32] + + "@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9": + resolution: + { + integrity: sha512-A7kN248viWvb8eZMzQu024TBKGoyoVYBsDG2DtoP8u2pzwoh5yDqUL291u01o4f8uzpUHq8mfwQJmcGChFu8KQ==, + } + cpu: [arm64] + os: [win32] + + "@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9": + resolution: + { + integrity: sha512-DzKN7iEYjAP8AK8F2G2aCej3fk43Y/EQrVrR3gF0XREes56chjQ7bXIhw819jv74BbxGdnpPcslhet/cgt7WRA==, + } + cpu: [ia32] + os: [win32] + + "@rolldown/binding-win32-x64-msvc@1.0.0-beta.9": + resolution: + { + integrity: sha512-GMWgTvvbZ8TfBsAiJpoz4SRq3IN3aUMn0rYm8q4I8dcEk4J1uISyfb6ZMzvqW+cvScTWVKWZNqnrmYOKLLUt4w==, + } + cpu: [x64] + os: [win32] + + "@rolldown/pluginutils@1.0.0-beta.9": + resolution: + { + integrity: sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==, + } + + "@rollup/rollup-android-arm-eabi@4.44.0": + resolution: + { + integrity: sha512-xEiEE5oDW6tK4jXCAyliuntGR+amEMO7HLtdSshVuhFnKTYoeYMyXQK7pLouAJJj5KHdwdn87bfHAR2nSdNAUA==, + } + cpu: [arm] + os: [android] + + "@rollup/rollup-android-arm64@4.44.0": + resolution: + { + integrity: sha512-uNSk/TgvMbskcHxXYHzqwiyBlJ/lGcv8DaUfcnNwict8ba9GTTNxfn3/FAoFZYgkaXXAdrAA+SLyKplyi349Jw==, + } + cpu: [arm64] + os: [android] + + "@rollup/rollup-darwin-arm64@4.44.0": + resolution: + { + integrity: sha512-VGF3wy0Eq1gcEIkSCr8Ke03CWT+Pm2yveKLaDvq51pPpZza3JX/ClxXOCmTYYq3us5MvEuNRTaeyFThCKRQhOA==, + } + cpu: [arm64] + os: [darwin] + + "@rollup/rollup-darwin-x64@4.44.0": + resolution: + { + integrity: sha512-fBkyrDhwquRvrTxSGH/qqt3/T0w5Rg0L7ZIDypvBPc1/gzjJle6acCpZ36blwuwcKD/u6oCE/sRWlUAcxLWQbQ==, + } + cpu: [x64] + os: [darwin] + + "@rollup/rollup-freebsd-arm64@4.44.0": + resolution: + { + integrity: sha512-u5AZzdQJYJXByB8giQ+r4VyfZP+walV+xHWdaFx/1VxsOn6eWJhK2Vl2eElvDJFKQBo/hcYIBg/jaKS8ZmKeNQ==, + } + cpu: [arm64] + os: [freebsd] + + "@rollup/rollup-freebsd-x64@4.44.0": + resolution: + { + integrity: sha512-qC0kS48c/s3EtdArkimctY7h3nHicQeEUdjJzYVJYR3ct3kWSafmn6jkNCA8InbUdge6PVx6keqjk5lVGJf99g==, + } + cpu: [x64] + os: [freebsd] + + "@rollup/rollup-linux-arm-gnueabihf@4.44.0": + resolution: + { + integrity: sha512-x+e/Z9H0RAWckn4V2OZZl6EmV0L2diuX3QB0uM1r6BvhUIv6xBPL5mrAX2E3e8N8rEHVPwFfz/ETUbV4oW9+lQ==, + } + cpu: [arm] + os: [linux] + + "@rollup/rollup-linux-arm-musleabihf@4.44.0": + resolution: + { + integrity: sha512-1exwiBFf4PU/8HvI8s80icyCcnAIB86MCBdst51fwFmH5dyeoWVPVgmQPcKrMtBQ0W5pAs7jBCWuRXgEpRzSCg==, + } + cpu: [arm] + os: [linux] + + "@rollup/rollup-linux-arm64-gnu@4.44.0": + resolution: + { + integrity: sha512-ZTR2mxBHb4tK4wGf9b8SYg0Y6KQPjGpR4UWwTFdnmjB4qRtoATZ5dWn3KsDwGa5Z2ZBOE7K52L36J9LueKBdOQ==, + } + cpu: [arm64] + os: [linux] + + "@rollup/rollup-linux-arm64-musl@4.44.0": + resolution: + { + integrity: sha512-GFWfAhVhWGd4r6UxmnKRTBwP1qmModHtd5gkraeW2G490BpFOZkFtem8yuX2NyafIP/mGpRJgTJ2PwohQkUY/Q==, + } + cpu: [arm64] + os: [linux] + + "@rollup/rollup-linux-loongarch64-gnu@4.44.0": + resolution: + { + integrity: sha512-xw+FTGcov/ejdusVOqKgMGW3c4+AgqrfvzWEVXcNP6zq2ue+lsYUgJ+5Rtn/OTJf7e2CbgTFvzLW2j0YAtj0Gg==, + } + cpu: [loong64] + os: [linux] + + "@rollup/rollup-linux-powerpc64le-gnu@4.44.0": + resolution: + { + integrity: sha512-bKGibTr9IdF0zr21kMvkZT4K6NV+jjRnBoVMt2uNMG0BYWm3qOVmYnXKzx7UhwrviKnmK46IKMByMgvpdQlyJQ==, + } + cpu: [ppc64] + os: [linux] + + "@rollup/rollup-linux-riscv64-gnu@4.44.0": + resolution: + { + integrity: sha512-vV3cL48U5kDaKZtXrti12YRa7TyxgKAIDoYdqSIOMOFBXqFj2XbChHAtXquEn2+n78ciFgr4KIqEbydEGPxXgA==, + } + cpu: [riscv64] + os: [linux] + + "@rollup/rollup-linux-riscv64-musl@4.44.0": + resolution: + { + integrity: sha512-TDKO8KlHJuvTEdfw5YYFBjhFts2TR0VpZsnLLSYmB7AaohJhM8ctDSdDnUGq77hUh4m/djRafw+9zQpkOanE2Q==, + } + cpu: [riscv64] + os: [linux] + + "@rollup/rollup-linux-s390x-gnu@4.44.0": + resolution: + { + integrity: sha512-8541GEyktXaw4lvnGp9m84KENcxInhAt6vPWJ9RodsB/iGjHoMB2Pp5MVBCiKIRxrxzJhGCxmNzdu+oDQ7kwRA==, + } + cpu: [s390x] + os: [linux] + + "@rollup/rollup-linux-x64-gnu@4.44.0": + resolution: + { + integrity: sha512-iUVJc3c0o8l9Sa/qlDL2Z9UP92UZZW1+EmQ4xfjTc1akr0iUFZNfxrXJ/R1T90h/ILm9iXEY6+iPrmYB3pXKjw==, + } + cpu: [x64] + os: [linux] + + "@rollup/rollup-linux-x64-musl@4.44.0": + resolution: + { + integrity: sha512-PQUobbhLTQT5yz/SPg116VJBgz+XOtXt8D1ck+sfJJhuEsMj2jSej5yTdp8CvWBSceu+WW+ibVL6dm0ptG5fcA==, + } + cpu: [x64] + os: [linux] + + "@rollup/rollup-win32-arm64-msvc@4.44.0": + resolution: + { + integrity: sha512-M0CpcHf8TWn+4oTxJfh7LQuTuaYeXGbk0eageVjQCKzYLsajWS/lFC94qlRqOlyC2KvRT90ZrfXULYmukeIy7w==, + } + cpu: [arm64] + os: [win32] + + "@rollup/rollup-win32-ia32-msvc@4.44.0": + resolution: + { + integrity: sha512-3XJ0NQtMAXTWFW8FqZKcw3gOQwBtVWP/u8TpHP3CRPXD7Pd6s8lLdH3sHWh8vqKCyyiI8xW5ltJScQmBU9j7WA==, + } + cpu: [ia32] + os: [win32] + + "@rollup/rollup-win32-x64-msvc@4.44.0": + resolution: + { + integrity: sha512-Q2Mgwt+D8hd5FIPUuPDsvPR7Bguza6yTkJxspDGkZj7tBRn2y4KSWYuIXpftFSjBra76TbKerCV7rgFPQrn+wQ==, + } + cpu: [x64] + os: [win32] + + "@sxzz/eslint-config@7.0.4": + resolution: + { + integrity: sha512-4o3stQQ5wVdQXJ4zWwHlfC5Mv489Ob6A7bmEnt2GGOr675eVV/IFqDdwu1MnbmcQVY5CVXsyhkBCNHQTjDqAiA==, + } + engines: { node: ">=20.0.0" } + peerDependencies: + "@unocss/eslint-plugin": ">=65.0.0" + eslint: ^9.5.0 + peerDependenciesMeta: + "@unocss/eslint-plugin": + optional: true + + "@sxzz/prettier-config@2.2.1": + resolution: + { + integrity: sha512-4eKrQdzJpMOFrUD9rFm1IfVkpchPvnPOObJvnX+DQB0KHRtHbU0vBwSpOLHioxLPYFwJGjSl6NC0trrCDkCtsA==, + } + + "@tybys/wasm-util@0.9.0": + resolution: + { + integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==, + } + + "@types/chai@5.2.2": + resolution: + { + integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==, + } + + "@types/debug@4.1.12": + resolution: + { + integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==, + } + + "@types/deep-eql@4.0.2": + resolution: + { + integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==, + } + + "@types/estree@1.0.8": + resolution: + { + integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==, + } + + "@types/json-schema@7.0.15": + resolution: + { + integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==, + } + + "@types/mdast@4.0.4": + resolution: + { + integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==, + } + + "@types/ms@2.1.0": + resolution: + { + integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==, + } + + "@types/node@22.15.32": + resolution: + { + integrity: sha512-3jigKqgSjsH6gYZv2nEsqdXfZqIFGAV36XYYjf9KGZ3PSG+IhLecqPnI310RvjutyMwifE2hhhNEklOUrvx/wA==, + } + + "@types/unist@3.0.3": + resolution: + { + integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==, + } + + "@typescript-eslint/eslint-plugin@8.34.1": + resolution: + { + integrity: sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + "@typescript-eslint/parser": ^8.34.1 + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <5.9.0" + + "@typescript-eslint/parser@8.34.1": + resolution: + { + integrity: sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <5.9.0" + + "@typescript-eslint/project-service@8.34.1": + resolution: + { + integrity: sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: ">=4.8.4 <5.9.0" + + "@typescript-eslint/scope-manager@8.34.1": + resolution: + { + integrity: sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@typescript-eslint/tsconfig-utils@8.34.1": + resolution: + { + integrity: sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: ">=4.8.4 <5.9.0" + + "@typescript-eslint/type-utils@8.34.1": + resolution: + { + integrity: sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <5.9.0" + + "@typescript-eslint/types@8.34.1": + resolution: + { + integrity: sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@typescript-eslint/typescript-estree@8.34.1": + resolution: + { + integrity: sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + typescript: ">=4.8.4 <5.9.0" + + "@typescript-eslint/utils@8.34.1": + resolution: + { + integrity: sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <5.9.0" + + "@typescript-eslint/visitor-keys@8.34.1": + resolution: + { + integrity: sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + "@unrs/resolver-binding-android-arm-eabi@1.9.1": + resolution: + { + integrity: sha512-dd7yIp1hfJFX9ZlVLQRrh/Re9WMUHHmF9hrKD1yIvxcyNr2BhQ3xc1upAVhy8NijadnCswAxWQu8MkkSMC1qXQ==, + } + cpu: [arm] + os: [android] + + "@unrs/resolver-binding-android-arm64@1.9.1": + resolution: + { + integrity: sha512-EzUPcMFtDVlo5yrbzMqUsGq3HnLXw+3ZOhSd7CUaDmbTtnrzM+RO2ntw2dm2wjbbc5djWj3yX0wzbbg8pLhx8g==, + } + cpu: [arm64] + os: [android] + + "@unrs/resolver-binding-darwin-arm64@1.9.1": + resolution: + { + integrity: sha512-nB+dna3q4kOleKFcSZJ/wDXIsAd1kpMO9XrVAt8tG3RDWJ6vi+Ic6bpz4cmg5tWNeCfHEY4KuqJCB+pKejPEmQ==, + } + cpu: [arm64] + os: [darwin] + + "@unrs/resolver-binding-darwin-x64@1.9.1": + resolution: + { + integrity: sha512-aKWHCrOGaCGwZcekf3TnczQoBxk5w//W3RZ4EQyhux6rKDwBPgDU9Y2yGigCV1Z+8DWqZgVGQi+hdpnlSy3a1w==, + } + cpu: [x64] + os: [darwin] + + "@unrs/resolver-binding-freebsd-x64@1.9.1": + resolution: + { + integrity: sha512-4dIEMXrXt0UqDVgrsUd1I+NoIzVQWXy/CNhgpfS75rOOMK/4Abn0Mx2M2gWH4Mk9+ds/ASAiCmqoUFynmMY5hA==, + } + cpu: [x64] + os: [freebsd] + + "@unrs/resolver-binding-linux-arm-gnueabihf@1.9.1": + resolution: + { + integrity: sha512-vtvS13IXPs1eE8DuS/soiosqMBeyh50YLRZ+p7EaIKAPPeevRnA9G/wu/KbVt01ZD5qiGjxS+CGIdVC7I6gTOw==, + } + cpu: [arm] + os: [linux] + + "@unrs/resolver-binding-linux-arm-musleabihf@1.9.1": + resolution: + { + integrity: sha512-BfdnN6aZ7NcX8djW8SR6GOJc+K+sFhWRF4vJueVE0vbUu5N1bLnBpxJg1TGlhSyo+ImC4SR0jcNiKN0jdoxt+A==, + } + cpu: [arm] + os: [linux] + + "@unrs/resolver-binding-linux-arm64-gnu@1.9.1": + resolution: + { + integrity: sha512-Jhge7lFtH0QqfRz2PyJjJXWENqywPteITd+nOS0L6AhbZli+UmEyGBd2Sstt1c+l9C+j/YvKTl9wJo9PPmsFNg==, + } + cpu: [arm64] + os: [linux] + + "@unrs/resolver-binding-linux-arm64-musl@1.9.1": + resolution: + { + integrity: sha512-ofdK/ow+ZSbSU0pRoB7uBaiRHeaAOYQFU5Spp87LdcPL/P1RhbCTMSIYVb61XWzsVEmYKjHFtoIE0wxP6AFvrA==, + } + cpu: [arm64] + os: [linux] + + "@unrs/resolver-binding-linux-ppc64-gnu@1.9.1": + resolution: + { + integrity: sha512-eC8SXVn8de67HacqU7PoGdHA+9tGbqfEdD05AEFRAB81ejeQtNi5Fx7lPcxpLH79DW0BnMAHau3hi4RVkHfSCw==, + } + cpu: [ppc64] + os: [linux] + + "@unrs/resolver-binding-linux-riscv64-gnu@1.9.1": + resolution: + { + integrity: sha512-fIkwvAAQ41kfoGWfzeJ33iLGShl0JEDZHrMnwTHMErUcPkaaZRJYjQjsFhMl315NEQ4mmTlC+2nfK/J2IszDOw==, + } + cpu: [riscv64] + os: [linux] + + "@unrs/resolver-binding-linux-riscv64-musl@1.9.1": + resolution: + { + integrity: sha512-RAAszxImSOFLk44aLwnSqpcOdce8sBcxASledSzuFAd8Q5ZhhVck472SisspnzHdc7THCvGXiUeZ2hOC7NUoBQ==, + } + cpu: [riscv64] + os: [linux] + + "@unrs/resolver-binding-linux-s390x-gnu@1.9.1": + resolution: + { + integrity: sha512-QoP9vkY+THuQdZi05bA6s6XwFd6HIz3qlx82v9bTOgxeqin/3C12Ye7f7EOD00RQ36OtOPWnhEMMm84sv7d1XQ==, + } + cpu: [s390x] + os: [linux] + + "@unrs/resolver-binding-linux-x64-gnu@1.9.1": + resolution: + { + integrity: sha512-/p77cGN/h9zbsfCseAP5gY7tK+7+DdM8fkPfr9d1ye1fsF6bmtGbtZN6e/8j4jCZ9NEIBBkT0GhdgixSelTK9g==, + } + cpu: [x64] + os: [linux] + + "@unrs/resolver-binding-linux-x64-musl@1.9.1": + resolution: + { + integrity: sha512-wInTqT3Bu9u50mDStEig1v8uxEL2Ht+K8pir/YhyyrM5ordJtxoqzsL1vR/CQzOJuDunUTrDkMM0apjW/d7/PA==, + } + cpu: [x64] + os: [linux] + + "@unrs/resolver-binding-wasm32-wasi@1.9.1": + resolution: + { + integrity: sha512-eNwqO5kUa+1k7yFIircwwiniKWA0UFHo2Cfm8LYgkh9km7uMad+0x7X7oXbQonJXlqfitBTSjhA0un+DsHIrhw==, + } + engines: { node: ">=14.0.0" } + cpu: [wasm32] + + "@unrs/resolver-binding-win32-arm64-msvc@1.9.1": + resolution: + { + integrity: sha512-Eaz1xMUnoa2mFqh20mPqSdbYl6crnk8HnIXDu6nsla9zpgZJZO8w3c1gvNN/4Eb0RXRq3K9OG6mu8vw14gIqiA==, + } + cpu: [arm64] + os: [win32] + + "@unrs/resolver-binding-win32-ia32-msvc@1.9.1": + resolution: + { + integrity: sha512-H/+d+5BGlnEQif0gnwWmYbYv7HJj563PUKJfn8PlmzF8UmF+8KxdvXdwCsoOqh4HHnENnoLrav9NYBrv76x1wQ==, + } + cpu: [ia32] + os: [win32] + + "@unrs/resolver-binding-win32-x64-msvc@1.9.1": + resolution: + { + integrity: sha512-rS86wI4R6cknYM3is3grCb/laE8XBEbpWAMSIPjYfmYp75KL5dT87jXF2orDa4tQYg5aajP5G8Fgh34dRyR+Rw==, + } + cpu: [x64] + os: [win32] + + "@vitest/expect@3.2.4": + resolution: + { + integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==, + } + + "@vitest/mocker@3.2.4": + resolution: + { + integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==, + } + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + "@vitest/pretty-format@3.2.4": + resolution: + { + integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==, + } + + "@vitest/runner@3.2.4": + resolution: + { + integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==, + } + + "@vitest/snapshot@3.2.4": + resolution: + { + integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==, + } + + "@vitest/spy@3.2.4": + resolution: + { + integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==, + } + + "@vitest/utils@3.2.4": + resolution: + { + integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==, + } + + acorn-jsx@5.3.2: + resolution: + { + integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, + } + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: + { + integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==, + } + engines: { node: ">=0.4.0" } + hasBin: true + + ajv@6.12.6: + resolution: + { + integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==, + } + + ansi-styles@4.3.0: + resolution: + { + integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, + } + engines: { node: ">=8" } + + ansis@4.1.0: + resolution: + { + integrity: sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==, + } + engines: { node: ">=14" } + + are-docs-informative@0.0.2: + resolution: + { + integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==, + } + engines: { node: ">=14" } + + argparse@2.0.1: + resolution: + { + integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, + } + + args-tokenizer@0.3.0: + resolution: + { + integrity: sha512-xXAd7G2Mll5W8uo37GETpQ2VrE84M181Z7ugHFGQnJZ50M2mbOv0osSZ9VsSgPfJQ+LVG0prSi0th+ELMsno7Q==, + } + + assertion-error@2.0.1: + resolution: + { + integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==, + } + engines: { node: ">=12" } + + ast-kit@2.1.0: + resolution: + { + integrity: sha512-ROM2LlXbZBZVk97crfw8PGDOBzzsJvN2uJCmwswvPUNyfH14eg90mSN3xNqsri1JS1G9cz0VzeDUhxJkTrr4Ew==, + } + engines: { node: ">=20.18.0" } + + balanced-match@1.0.2: + resolution: + { + integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, + } + + birpc@2.4.0: + resolution: + { + integrity: sha512-5IdNxTyhXHv2UlgnPHQ0h+5ypVmkrYHzL8QT+DwFZ//2N/oNV8Ch+BCRmTJ3x6/z9Axo/cXYBc9eprsUVK/Jsg==, + } + + boolbase@1.0.0: + resolution: + { + integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==, + } + + brace-expansion@1.1.12: + resolution: + { + integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==, + } + + brace-expansion@2.0.2: + resolution: + { + integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==, + } + + braces@3.0.3: + resolution: + { + integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==, + } + engines: { node: ">=8" } + + browserslist@4.25.0: + resolution: + { + integrity: sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==, + } + engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } + hasBin: true + + builtin-modules@5.0.0: + resolution: + { + integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==, + } + engines: { node: ">=18.20" } + + bumpp@10.2.0: + resolution: + { + integrity: sha512-1EJ2NG3M3WYJj4m+GtcxNH6Y7zMQ8q68USMoUGKjM6qFTVXSXCnTxcQSUDV7j4KjLVbk2uK6345Z+6RKOv0w5A==, + } + engines: { node: ">=18" } + hasBin: true + + c12@3.0.4: + resolution: + { + integrity: sha512-t5FaZTYbbCtvxuZq9xxIruYydrAGsJ+8UdP0pZzMiK2xl/gNiSOy0OxhLzHUEEb0m1QXYqfzfvyIFEmz/g9lqg==, + } + peerDependencies: + magicast: ^0.3.5 + peerDependenciesMeta: + magicast: + optional: true + + cac@6.7.14: + resolution: + { + integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==, + } + engines: { node: ">=8" } + + callsites@3.1.0: + resolution: + { + integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==, + } + engines: { node: ">=6" } + + caniuse-lite@1.0.30001724: + resolution: + { + integrity: sha512-WqJo7p0TbHDOythNTqYujmaJTvtYRZrjpP8TCvH6Vb9CYJerJNKamKzIWOM4BkQatWj9H2lYulpdAQNBe7QhNA==, + } + + ccount@2.0.1: + resolution: + { + integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==, + } + + chai@5.2.0: + resolution: + { + integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==, + } + engines: { node: ">=12" } + + chalk@4.1.2: + resolution: + { + integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==, + } + engines: { node: ">=10" } + + character-entities@2.0.2: + resolution: + { + integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==, + } + + check-error@2.1.1: + resolution: + { + integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==, + } + engines: { node: ">= 16" } + + chokidar@4.0.3: + resolution: + { + integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==, + } + engines: { node: ">= 14.16.0" } + + ci-info@4.2.0: + resolution: + { + integrity: sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==, + } + engines: { node: ">=8" } + + citty@0.1.6: + resolution: + { + integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==, + } + + clean-regexp@1.0.0: + resolution: + { + integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==, + } + engines: { node: ">=4" } + + color-convert@2.0.1: + resolution: + { + integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, + } + engines: { node: ">=7.0.0" } + + color-name@1.1.4: + resolution: + { + integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, + } + + comment-parser@1.4.1: + resolution: + { + integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==, + } + engines: { node: ">= 12.0.0" } + + concat-map@0.0.1: + resolution: + { + integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, + } + + confbox@0.1.8: + resolution: + { + integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==, + } + + confbox@0.2.2: + resolution: + { + integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==, + } + + consola@3.4.2: + resolution: + { + integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==, + } + engines: { node: ^14.18.0 || >=16.10.0 } + + core-js-compat@3.43.0: + resolution: + { + integrity: sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==, + } + + cross-spawn@7.0.6: + resolution: + { + integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==, + } + engines: { node: ">= 8" } + + cssesc@3.0.0: + resolution: + { + integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==, + } + engines: { node: ">=4" } + hasBin: true + + debug@4.4.1: + resolution: + { + integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==, + } + engines: { node: ">=6.0" } + peerDependencies: + supports-color: "*" + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.2.0: + resolution: + { + integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==, + } + + deep-eql@5.0.2: + resolution: + { + integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==, + } + engines: { node: ">=6" } + + deep-is@0.1.4: + resolution: + { + integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, + } + + defu@6.1.4: + resolution: + { + integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==, + } + + dequal@2.0.3: + resolution: + { + integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==, + } + engines: { node: ">=6" } + + destr@2.0.5: + resolution: + { + integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==, + } + + devlop@1.1.0: + resolution: + { + integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==, + } + + diff@8.0.2: + resolution: + { + integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==, + } + engines: { node: ">=0.3.1" } + + dotenv@16.5.0: + resolution: + { + integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==, + } + engines: { node: ">=12" } + + dts-resolver@2.1.1: + resolution: + { + integrity: sha512-3BiGFhB6mj5Kv+W2vdJseQUYW+SKVzAFJL6YNP6ursbrwy1fXHRotfHi3xLNxe4wZl/K8qbAFeCDjZLjzqxxRw==, + } + engines: { node: ">=20.18.0" } + peerDependencies: + oxc-resolver: ">=11.0.0" + peerDependenciesMeta: + oxc-resolver: + optional: true + + electron-to-chromium@1.5.171: + resolution: + { + integrity: sha512-scWpzXEJEMrGJa4Y6m/tVotb0WuvNmasv3wWVzUAeCgKU0ToFOhUW6Z+xWnRQANMYGxN4ngJXIThgBJOqzVPCQ==, + } + + empathic@1.1.0: + resolution: + { + integrity: sha512-rsPft6CK3eHtrlp9Y5ALBb+hfK+DWnA4WFebbazxjWyx8vSm3rZeoM3z9irsjcqO3PYRzlfv27XIB4tz2DV7RA==, + } + engines: { node: ">=14" } + + enhanced-resolve@5.18.1: + resolution: + { + integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==, + } + engines: { node: ">=10.13.0" } + + es-module-lexer@1.7.0: + resolution: + { + integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==, + } + + esbuild@0.25.5: + resolution: + { + integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==, + } + engines: { node: ">=18" } + hasBin: true + + escalade@3.2.0: + resolution: + { + integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==, + } + engines: { node: ">=6" } + + escape-string-regexp@1.0.5: + resolution: + { + integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==, + } + engines: { node: ">=0.8.0" } + + escape-string-regexp@4.0.0: + resolution: + { + integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==, + } + engines: { node: ">=10" } + + escape-string-regexp@5.0.0: + resolution: + { + integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==, + } + engines: { node: ">=12" } + + eslint-compat-utils@0.5.1: + resolution: + { + integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==, + } + engines: { node: ">=12" } + peerDependencies: + eslint: ">=6.0.0" + + eslint-compat-utils@0.6.5: + resolution: + { + integrity: sha512-vAUHYzue4YAa2hNACjB8HvUQj5yehAZgiClyFVVom9cP8z5NSFq3PwB/TtJslN2zAMgRX6FCFCjYBbQh71g5RQ==, + } + engines: { node: ">=12" } + peerDependencies: + eslint: ">=6.0.0" + + eslint-config-flat-gitignore@2.1.0: + resolution: + { + integrity: sha512-cJzNJ7L+psWp5mXM7jBX+fjHtBvvh06RBlcweMhKD8jWqQw0G78hOW5tpVALGHGFPsBV+ot2H+pdDGJy6CV8pA==, + } + peerDependencies: + eslint: ^9.5.0 + + eslint-config-prettier@10.1.5: + resolution: + { + integrity: sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==, + } + hasBin: true + peerDependencies: + eslint: ">=7.0.0" + + eslint-flat-config-utils@2.1.0: + resolution: + { + integrity: sha512-6fjOJ9tS0k28ketkUcQ+kKptB4dBZY2VijMZ9rGn8Cwnn1SH0cZBoPXT8AHBFHxmHcLFQK9zbELDinZ2Mr1rng==, + } + + eslint-import-context@0.1.8: + resolution: + { + integrity: sha512-bq+F7nyc65sKpZGT09dY0S0QrOnQtuDVIfyTGQ8uuvtMIF7oHp6CEP3mouN0rrnYF3Jqo6Ke0BfU/5wASZue1w==, + } + engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } + peerDependencies: + unrs-resolver: ^1.0.0 + peerDependenciesMeta: + unrs-resolver: + optional: true + + eslint-json-compat-utils@0.2.1: + resolution: + { + integrity: sha512-YzEodbDyW8DX8bImKhAcCeu/L31Dd/70Bidx2Qex9OFUtgzXLqtfWL4Hr5fM/aCCB8QUZLuJur0S9k6UfgFkfg==, + } + engines: { node: ">=12" } + peerDependencies: + "@eslint/json": "*" + eslint: "*" + jsonc-eslint-parser: ^2.4.0 + peerDependenciesMeta: + "@eslint/json": + optional: true + + eslint-plugin-antfu@3.1.1: + resolution: + { + integrity: sha512-7Q+NhwLfHJFvopI2HBZbSxWXngTwBLKxW1AGXLr2lEGxcEIK/AsDs8pn8fvIizl5aZjBbVbVK5ujmMpBe4Tvdg==, + } + peerDependencies: + eslint: "*" + + eslint-plugin-command@3.3.1: + resolution: + { + integrity: sha512-fBVTXQ2y48TVLT0+4A6PFINp7GcdIailHAXbvPBixE7x+YpYnNQhFZxTdvnb+aWk+COgNebQKen/7m4dmgyWAw==, + } + peerDependencies: + eslint: "*" + + eslint-plugin-de-morgan@1.3.0: + resolution: + { + integrity: sha512-UHCQ4XyDaEeSmk7lI0O1TEcsSBkvdyY8FV4H9TN2DvBfKV45SJR/qZ/rYwzt8JSBIbMXy8S0vNBhzngnpIreJw==, + } + engines: { node: ^18.0.0 || >=20.0.0 } + peerDependencies: + eslint: ">=8.0.0" + + eslint-plugin-es-x@7.8.0: + resolution: + { + integrity: sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==, + } + engines: { node: ^14.18.0 || >=16.0.0 } + peerDependencies: + eslint: ">=8" + + eslint-plugin-import-x@4.15.2: + resolution: + { + integrity: sha512-J5gx7sN6DTm0LRT//eP3rVVQ2Yi4hrX0B+DbWxa5er8PZ6JjLo9GUBwogIFvEDdwJaSqZplpQT+haK/cXhb7VQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + "@typescript-eslint/utils": ^8.0.0 + eslint: ^8.57.0 || ^9.0.0 + eslint-import-resolver-node: "*" + peerDependenciesMeta: + "@typescript-eslint/utils": + optional: true + eslint-import-resolver-node: + optional: true + + eslint-plugin-jsdoc@51.2.1: + resolution: + { + integrity: sha512-iE2qpG/kaA9xXfEcTNSsxNvH5O8+o38VBGLwl2oZisQaM1JRGftTLJAGQrj7YZjSkp3n9VCrNTjOpo3ONhTApQ==, + } + engines: { node: ">=20.11.0" } + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 + + eslint-plugin-jsonc@2.20.1: + resolution: + { + integrity: sha512-gUzIwQHXx7ZPypUoadcyRi4WbHW2TPixDr0kqQ4miuJBU0emJmyGTlnaT3Og9X2a8R1CDayN9BFSq5weGWbTng==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + peerDependencies: + eslint: ">=6.0.0" + + eslint-plugin-n@17.20.0: + resolution: + { + integrity: sha512-IRSoatgB/NQJZG5EeTbv/iAx1byOGdbbyhQrNvWdCfTnmPxUT0ao9/eGOeG7ljD8wJBsxwE8f6tES5Db0FRKEw==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ">=8.23.0" + + eslint-plugin-perfectionist@4.15.0: + resolution: + { + integrity: sha512-pC7PgoXyDnEXe14xvRUhBII8A3zRgggKqJFx2a82fjrItDs1BSI7zdZnQtM2yQvcyod6/ujmzb7ejKPx8lZTnw==, + } + engines: { node: ^18.0.0 || >=20.0.0 } + peerDependencies: + eslint: ">=8.45.0" + + eslint-plugin-pnpm@0.3.1: + resolution: + { + integrity: sha512-vi5iHoELIAlBbX4AW8ZGzU3tUnfxuXhC/NKo3qRcI5o9igbz6zJUqSlQ03bPeMqWIGTPatZnbWsNR1RnlNERNQ==, + } + peerDependencies: + eslint: ^9.0.0 + + eslint-plugin-prettier@5.5.0: + resolution: + { + integrity: sha512-8qsOYwkkGrahrgoUv76NZi23koqXOGiiEzXMrT8Q7VcYaUISR+5MorIUxfWqYXN0fN/31WbSrxCxFkVQ43wwrA==, + } + engines: { node: ^14.18.0 || >=16.0.0 } + peerDependencies: + "@types/eslint": ">=8.0.0" + eslint: ">=8.0.0" + eslint-config-prettier: ">= 7.0.0 <10.0.0 || >=10.1.0" + prettier: ">=3.0.0" + peerDependenciesMeta: + "@types/eslint": + optional: true + eslint-config-prettier: + optional: true + + eslint-plugin-regexp@2.9.0: + resolution: + { + integrity: sha512-9WqJMnOq8VlE/cK+YAo9C9YHhkOtcEtEk9d12a+H7OSZFwlpI6stiHmYPGa2VE0QhTzodJyhlyprUaXDZLgHBw==, + } + engines: { node: ^18 || >=20 } + peerDependencies: + eslint: ">=8.44.0" + + eslint-plugin-sxzz@0.3.0: + resolution: + { + integrity: sha512-Zf5ubmi+oPqIYAZFJJYvUN7+cyuyShfRpPCLR245PYjYguay/eLDo1c671V8DJqURzk2B6rhhGJAfA9oJDdApA==, + } + engines: { node: ">=20.18.0" } + peerDependencies: + eslint: "*" + + eslint-plugin-unicorn@59.0.1: + resolution: + { + integrity: sha512-EtNXYuWPUmkgSU2E7Ttn57LbRREQesIP1BiLn7OZLKodopKfDXfBUkC/0j6mpw2JExwf43Uf3qLSvrSvppgy8Q==, + } + engines: { node: ^18.20.0 || ^20.10.0 || >=21.0.0 } + peerDependencies: + eslint: ">=9.22.0" + + eslint-plugin-unused-imports@4.1.4: + resolution: + { + integrity: sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ==, + } + peerDependencies: + "@typescript-eslint/eslint-plugin": ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0 + eslint: ^9.0.0 || ^8.0.0 + peerDependenciesMeta: + "@typescript-eslint/eslint-plugin": + optional: true + + eslint-plugin-vue@10.2.0: + resolution: + { + integrity: sha512-tl9s+KN3z0hN2b8fV2xSs5ytGl7Esk1oSCxULLwFcdaElhZ8btYYZFrWxvh4En+czrSDtuLCeCOGa8HhEZuBdQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + vue-eslint-parser: ^10.0.0 + + eslint-plugin-yml@1.18.0: + resolution: + { + integrity: sha512-9NtbhHRN2NJa/s3uHchO3qVVZw0vyOIvWlXWGaKCr/6l3Go62wsvJK5byiI6ZoYztDsow4GnS69BZD3GnqH3hA==, + } + engines: { node: ^14.17.0 || >=16.0.0 } + peerDependencies: + eslint: ">=6.0.0" + + eslint-scope@8.4.0: + resolution: + { + integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + eslint-visitor-keys@3.4.3: + resolution: + { + integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + + eslint-visitor-keys@4.2.1: + resolution: + { + integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + eslint@9.29.0: + resolution: + { + integrity: sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + hasBin: true + peerDependencies: + jiti: "*" + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: + { + integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + + espree@9.6.1: + resolution: + { + integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + + esquery@1.6.0: + resolution: + { + integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==, + } + engines: { node: ">=0.10" } + + esrecurse@4.3.0: + resolution: + { + integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==, + } + engines: { node: ">=4.0" } + + estraverse@5.3.0: + resolution: + { + integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==, + } + engines: { node: ">=4.0" } + + estree-walker@3.0.3: + resolution: + { + integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==, + } + + esutils@2.0.3: + resolution: + { + integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==, + } + engines: { node: ">=0.10.0" } + + expect-type@1.2.1: + resolution: + { + integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==, + } + engines: { node: ">=12.0.0" } + + exsolve@1.0.7: + resolution: + { + integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==, + } + + fast-deep-equal@3.1.3: + resolution: + { + integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, + } + + fast-diff@1.3.0: + resolution: + { + integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==, + } + + fast-glob@3.3.3: + resolution: + { + integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==, + } + engines: { node: ">=8.6.0" } + + fast-json-stable-stringify@2.1.0: + resolution: + { + integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==, + } + + fast-levenshtein@2.0.6: + resolution: + { + integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==, + } + + fastq@1.19.1: + resolution: + { + integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==, + } + + fault@2.0.1: + resolution: + { + integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==, + } + + fdir@6.4.6: + resolution: + { + integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==, + } + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: + { + integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==, + } + engines: { node: ">=16.0.0" } + + fill-range@7.1.1: + resolution: + { + integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==, + } + engines: { node: ">=8" } + + find-up-simple@1.0.1: + resolution: + { + integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==, + } + engines: { node: ">=18" } + + find-up@5.0.0: + resolution: + { + integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==, + } + engines: { node: ">=10" } + + flat-cache@4.0.1: + resolution: + { + integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==, + } + engines: { node: ">=16" } + + flatted@3.3.3: + resolution: + { + integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==, + } + + format@0.2.2: + resolution: + { + integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==, + } + engines: { node: ">=0.4.x" } + + fsevents@2.3.3: + resolution: + { + integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, + } + engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } + os: [darwin] + + get-tsconfig@4.10.1: + resolution: + { + integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==, + } + + giget@2.0.0: + resolution: + { + integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==, + } + hasBin: true + + github-slugger@2.0.0: + resolution: + { + integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==, + } + + glob-parent@5.1.2: + resolution: + { + integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==, + } + engines: { node: ">= 6" } + + glob-parent@6.0.2: + resolution: + { + integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==, + } + engines: { node: ">=10.13.0" } + + globals@14.0.0: + resolution: + { + integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==, + } + engines: { node: ">=18" } + + globals@15.15.0: + resolution: + { + integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==, + } + engines: { node: ">=18" } + + globals@16.2.0: + resolution: + { + integrity: sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==, + } + engines: { node: ">=18" } + + graceful-fs@4.2.11: + resolution: + { + integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==, + } + + graphemer@1.4.0: + resolution: + { + integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==, + } + + happy-dom@17.6.3: + resolution: + { + integrity: sha512-UVIHeVhxmxedbWPCfgS55Jg2rDfwf2BCKeylcPSqazLz5w3Kri7Q4xdBJubsr/+VUzFLh0VjIvh13RaDA2/Xug==, + } + engines: { node: ">=20.0.0" } + + has-flag@4.0.0: + resolution: + { + integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==, + } + engines: { node: ">=8" } + + hookable@5.5.3: + resolution: + { + integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==, + } + + ignore@5.3.2: + resolution: + { + integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==, + } + engines: { node: ">= 4" } + + ignore@7.0.5: + resolution: + { + integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==, + } + engines: { node: ">= 4" } + + import-fresh@3.3.1: + resolution: + { + integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==, + } + engines: { node: ">=6" } + + imurmurhash@0.1.4: + resolution: + { + integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==, + } + engines: { node: ">=0.8.19" } + + indent-string@5.0.0: + resolution: + { + integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==, + } + engines: { node: ">=12" } + + is-builtin-module@5.0.0: + resolution: + { + integrity: sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA==, + } + engines: { node: ">=18.20" } + + is-extglob@2.1.1: + resolution: + { + integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==, + } + engines: { node: ">=0.10.0" } + + is-glob@4.0.3: + resolution: + { + integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==, + } + engines: { node: ">=0.10.0" } + + is-number@7.0.0: + resolution: + { + integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==, + } + engines: { node: ">=0.12.0" } + + isexe@2.0.0: + resolution: + { + integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, + } + + jiti@2.4.2: + resolution: + { + integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==, + } + hasBin: true + + js-tokens@9.0.1: + resolution: + { + integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==, + } + + js-yaml@4.1.0: + resolution: + { + integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==, + } + hasBin: true + + jsdoc-type-pratt-parser@4.1.0: + resolution: + { + integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==, + } + engines: { node: ">=12.0.0" } + + jsesc@3.0.2: + resolution: + { + integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==, + } + engines: { node: ">=6" } + hasBin: true + + jsesc@3.1.0: + resolution: + { + integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==, + } + engines: { node: ">=6" } + hasBin: true + + json-buffer@3.0.1: + resolution: + { + integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==, + } + + json-schema-traverse@0.4.1: + resolution: + { + integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==, + } + + json-stable-stringify-without-jsonify@1.0.1: + resolution: + { + integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==, + } + + jsonc-eslint-parser@2.4.0: + resolution: + { + integrity: sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==, + } + engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } + + jsonc-parser@3.3.1: + resolution: + { + integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==, + } + + keyv@4.5.4: + resolution: + { + integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==, + } + + levn@0.4.1: + resolution: + { + integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==, + } + engines: { node: ">= 0.8.0" } + + local-pkg@1.1.1: + resolution: + { + integrity: sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==, + } + engines: { node: ">=14" } + + locate-path@6.0.0: + resolution: + { + integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==, + } + engines: { node: ">=10" } + + lodash.merge@4.6.2: + resolution: + { + integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==, + } + + lodash@4.17.21: + resolution: + { + integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==, + } + + longest-streak@3.1.0: + resolution: + { + integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==, + } + + loupe@3.1.4: + resolution: + { + integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==, + } + + magic-string@0.30.17: + resolution: + { + integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==, + } + + markdown-table@3.0.4: + resolution: + { + integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==, + } + + mdast-util-find-and-replace@3.0.2: + resolution: + { + integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==, + } + + mdast-util-from-markdown@2.0.2: + resolution: + { + integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==, + } + + mdast-util-frontmatter@2.0.1: + resolution: + { + integrity: sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==, + } + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: + { + integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==, + } + + mdast-util-gfm-footnote@2.1.0: + resolution: + { + integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==, + } + + mdast-util-gfm-strikethrough@2.0.0: + resolution: + { + integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==, + } + + mdast-util-gfm-table@2.0.0: + resolution: + { + integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==, + } + + mdast-util-gfm-task-list-item@2.0.0: + resolution: + { + integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==, + } + + mdast-util-gfm@3.1.0: + resolution: + { + integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==, + } + + mdast-util-phrasing@4.1.0: + resolution: + { + integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==, + } + + mdast-util-to-markdown@2.1.2: + resolution: + { + integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==, + } + + mdast-util-to-string@4.0.0: + resolution: + { + integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==, + } + + merge2@1.4.1: + resolution: + { + integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==, + } + engines: { node: ">= 8" } + + micromark-core-commonmark@2.0.3: + resolution: + { + integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==, + } + + micromark-extension-frontmatter@2.0.0: + resolution: + { + integrity: sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==, + } + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: + { + integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==, + } + + micromark-extension-gfm-footnote@2.1.0: + resolution: + { + integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==, + } + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: + { + integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==, + } + + micromark-extension-gfm-table@2.1.1: + resolution: + { + integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==, + } + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: + { + integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==, + } + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: + { + integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==, + } + + micromark-extension-gfm@3.0.0: + resolution: + { + integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==, + } + + micromark-factory-destination@2.0.1: + resolution: + { + integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==, + } + + micromark-factory-label@2.0.1: + resolution: + { + integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==, + } + + micromark-factory-space@2.0.1: + resolution: + { + integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==, + } + + micromark-factory-title@2.0.1: + resolution: + { + integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==, + } + + micromark-factory-whitespace@2.0.1: + resolution: + { + integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==, + } + + micromark-util-character@2.1.1: + resolution: + { + integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==, + } + + micromark-util-chunked@2.0.1: + resolution: + { + integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==, + } + + micromark-util-classify-character@2.0.1: + resolution: + { + integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==, + } + + micromark-util-combine-extensions@2.0.1: + resolution: + { + integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==, + } + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: + { + integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==, + } + + micromark-util-decode-string@2.0.1: + resolution: + { + integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==, + } + + micromark-util-encode@2.0.1: + resolution: + { + integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==, + } + + micromark-util-html-tag-name@2.0.1: + resolution: + { + integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==, + } + + micromark-util-normalize-identifier@2.0.1: + resolution: + { + integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==, + } + + micromark-util-resolve-all@2.0.1: + resolution: + { + integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==, + } + + micromark-util-sanitize-uri@2.0.1: + resolution: + { + integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==, + } + + micromark-util-subtokenize@2.1.0: + resolution: + { + integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==, + } + + micromark-util-symbol@2.0.1: + resolution: + { + integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==, + } + + micromark-util-types@2.0.2: + resolution: + { + integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==, + } + + micromark@4.0.2: + resolution: + { + integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==, + } + + micromatch@4.0.8: + resolution: + { + integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==, + } + engines: { node: ">=8.6" } + + min-indent@1.0.1: + resolution: + { + integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==, + } + engines: { node: ">=4" } + + minimatch@10.0.3: + resolution: + { + integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==, + } + engines: { node: 20 || >=22 } + + minimatch@3.1.2: + resolution: + { + integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==, + } + + minimatch@9.0.5: + resolution: + { + integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==, + } + engines: { node: ">=16 || 14 >=14.17" } + + mlly@1.7.4: + resolution: + { + integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==, + } + + ms@2.1.3: + resolution: + { + integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, + } + + nanoid@3.3.11: + resolution: + { + integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==, + } + engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } + hasBin: true + + napi-postinstall@0.2.4: + resolution: + { + integrity: sha512-ZEzHJwBhZ8qQSbknHqYcdtQVr8zUgGyM/q6h6qAyhtyVMNrSgDhrC4disf03dYW0e+czXyLnZINnCTEkWy0eJg==, + } + engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } + hasBin: true + + natural-compare@1.4.0: + resolution: + { + integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, + } + + natural-orderby@5.0.0: + resolution: + { + integrity: sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg==, + } + engines: { node: ">=18" } + + node-fetch-native@1.6.6: + resolution: + { + integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==, + } + + node-releases@2.0.19: + resolution: + { + integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==, + } + + nth-check@2.1.1: + resolution: + { + integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==, + } + + nypm@0.6.0: + resolution: + { + integrity: sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg==, + } + engines: { node: ^14.16.0 || >=16.10.0 } + hasBin: true + + ohash@2.0.11: + resolution: + { + integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==, + } + + optionator@0.9.4: + resolution: + { + integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==, + } + engines: { node: ">= 0.8.0" } + + p-limit@3.1.0: + resolution: + { + integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==, + } + engines: { node: ">=10" } + + p-locate@5.0.0: + resolution: + { + integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==, + } + engines: { node: ">=10" } + + package-manager-detector@1.3.0: + resolution: + { + integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==, + } + + parent-module@1.0.1: + resolution: + { + integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==, + } + engines: { node: ">=6" } + + parse-imports-exports@0.2.4: + resolution: + { + integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==, + } + + parse-statements@1.0.11: + resolution: + { + integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==, + } + + path-exists@4.0.0: + resolution: + { + integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==, + } + engines: { node: ">=8" } + + path-key@3.1.1: + resolution: + { + integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==, + } + engines: { node: ">=8" } + + pathe@2.0.3: + resolution: + { + integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==, + } + + pathval@2.0.0: + resolution: + { + integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==, + } + engines: { node: ">= 14.16" } + + perfect-debounce@1.0.0: + resolution: + { + integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==, + } + + picocolors@1.1.1: + resolution: + { + integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, + } + + picomatch@2.3.1: + resolution: + { + integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==, + } + engines: { node: ">=8.6" } + + picomatch@4.0.2: + resolution: + { + integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==, + } + engines: { node: ">=12" } + + pkg-types@1.3.1: + resolution: + { + integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==, + } + + pkg-types@2.1.0: + resolution: + { + integrity: sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==, + } + + pluralize@8.0.0: + resolution: + { + integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==, + } + engines: { node: ">=4" } + + pnpm-workspace-yaml@0.3.1: + resolution: + { + integrity: sha512-3nW5RLmREmZ8Pm8MbPsO2RM+99RRjYd25ynj3NV0cFsN7CcEl4sDFzgoFmSyduFwxFQ2Qbu3y2UdCh6HlyUOeA==, + } + + postcss-selector-parser@6.1.2: + resolution: + { + integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==, + } + engines: { node: ">=4" } + + postcss@8.5.6: + resolution: + { + integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==, + } + engines: { node: ^10 || ^12 || >=14 } + + prelude-ls@1.2.1: + resolution: + { + integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==, + } + engines: { node: ">= 0.8.0" } + + prettier-linter-helpers@1.0.0: + resolution: + { + integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==, + } + engines: { node: ">=6.0.0" } + + prettier@3.6.0: + resolution: + { + integrity: sha512-ujSB9uXHJKzM/2GBuE0hBOUgC77CN3Bnpqa+g80bkv3T3A93wL/xlzDATHhnhkzifz/UE2SNOvmbTz5hSkDlHw==, + } + engines: { node: ">=14" } + hasBin: true + + punycode@2.3.1: + resolution: + { + integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==, + } + engines: { node: ">=6" } + + quansync@0.2.10: + resolution: + { + integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==, + } + + queue-microtask@1.2.3: + resolution: + { + integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, + } + + rc9@2.1.2: + resolution: + { + integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==, + } + + readdirp@4.1.2: + resolution: + { + integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==, + } + engines: { node: ">= 14.18.0" } + + refa@0.12.1: + resolution: + { + integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==, + } + engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } + + regexp-ast-analysis@0.7.1: + resolution: + { + integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==, + } + engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } + + regexp-tree@0.1.27: + resolution: + { + integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==, + } + hasBin: true + + regjsparser@0.12.0: + resolution: + { + integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==, + } + hasBin: true + + resolve-from@4.0.0: + resolution: + { + integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==, + } + engines: { node: ">=4" } + + resolve-pkg-maps@1.0.0: + resolution: + { + integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==, + } + + reusify@1.1.0: + resolution: + { + integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==, + } + engines: { iojs: ">=1.0.0", node: ">=0.10.0" } + + rolldown-plugin-dts@0.13.12: + resolution: + { + integrity: sha512-4QdQQfMOWNDLhmvoG3USAUpCm75dgwWk3IrK6SV9Fw2U5uwcSE7oQTqEcmsUGEBsNxZ58+gtM1oX38MMf0g5PA==, + } + engines: { node: ">=20.18.0" } + peerDependencies: + "@typescript/native-preview": ">=7.0.0-dev.20250601.1" + rolldown: ^1.0.0-beta.9 + typescript: ^5.0.0 + vue-tsc: ~2.2.0 + peerDependenciesMeta: + "@typescript/native-preview": + optional: true + typescript: + optional: true + vue-tsc: + optional: true + + rolldown@1.0.0-beta.9: + resolution: + { + integrity: sha512-ZgZky52n6iF0UainGKjptKGrOG4Con2S5sdc4C4y2Oj25D5PHAY8Y8E5f3M2TSd/zlhQs574JlMeTe3vREczSg==, + } + hasBin: true + peerDependencies: + "@oxc-project/runtime": 0.70.0 + peerDependenciesMeta: + "@oxc-project/runtime": + optional: true + + rollup@4.44.0: + resolution: + { + integrity: sha512-qHcdEzLCiktQIfwBq420pn2dP+30uzqYxv9ETm91wdt2R9AFcWfjNAmje4NWlnCIQ5RMTzVf0ZyisOKqHR6RwA==, + } + engines: { node: ">=18.0.0", npm: ">=8.0.0" } + hasBin: true + + run-parallel@1.2.0: + resolution: + { + integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==, + } + + scslre@0.3.0: + resolution: + { + integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==, + } + engines: { node: ^14.0.0 || >=16.0.0 } + + semver@7.7.2: + resolution: + { + integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==, + } + engines: { node: ">=10" } + hasBin: true + + shebang-command@2.0.0: + resolution: + { + integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==, + } + engines: { node: ">=8" } + + shebang-regex@3.0.0: + resolution: + { + integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==, + } + engines: { node: ">=8" } + + siginfo@2.0.0: + resolution: + { + integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==, + } + + source-map-js@1.2.1: + resolution: + { + integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==, + } + engines: { node: ">=0.10.0" } + + spdx-exceptions@2.5.0: + resolution: + { + integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==, + } + + spdx-expression-parse@4.0.0: + resolution: + { + integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==, + } + + spdx-license-ids@3.0.21: + resolution: + { + integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==, + } + + stable-hash-x@0.1.1: + resolution: + { + integrity: sha512-l0x1D6vhnsNUGPFVDx45eif0y6eedVC8nm5uACTrVFJFtl2mLRW17aWtVyxFCpn5t94VUPkjU8vSLwIuwwqtJQ==, + } + engines: { node: ">=12.0.0" } + + stackback@0.0.2: + resolution: + { + integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==, + } + + std-env@3.9.0: + resolution: + { + integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==, + } + + strip-indent@4.0.0: + resolution: + { + integrity: sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==, + } + engines: { node: ">=12" } + + strip-json-comments@3.1.1: + resolution: + { + integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==, + } + engines: { node: ">=8" } + + strip-literal@3.0.0: + resolution: + { + integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==, + } + + supports-color@7.2.0: + resolution: + { + integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==, + } + engines: { node: ">=8" } + + synckit@0.11.8: + resolution: + { + integrity: sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==, + } + engines: { node: ^14.18.0 || >=16.0.0 } + + tapable@2.2.2: + resolution: + { + integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==, + } + engines: { node: ">=6" } + + tinybench@2.9.0: + resolution: + { + integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==, + } + + tinyexec@0.3.2: + resolution: + { + integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==, + } + + tinyexec@1.0.1: + resolution: + { + integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==, + } + + tinyglobby@0.2.14: + resolution: + { + integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==, + } + engines: { node: ">=12.0.0" } + + tinypool@1.1.1: + resolution: + { + integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==, + } + engines: { node: ^18.0.0 || >=20.0.0 } + + tinyrainbow@2.0.0: + resolution: + { + integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==, + } + engines: { node: ">=14.0.0" } + + tinyspy@4.0.3: + resolution: + { + integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==, + } + engines: { node: ">=14.0.0" } + + to-regex-range@5.0.1: + resolution: + { + integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==, + } + engines: { node: ">=8.0" } + + ts-api-utils@2.1.0: + resolution: + { + integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==, + } + engines: { node: ">=18.12" } + peerDependencies: + typescript: ">=4.8.4" + + ts-declaration-location@1.0.7: + resolution: + { + integrity: sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==, + } + peerDependencies: + typescript: ">=4.0.0" + + tsdown@0.11.13: + resolution: + { + integrity: sha512-VSfoNm8MJXFdg7PJ4p2javgjMRiQQHpkP9N3iBBTrmCixcT6YZ9ZtqYMW3NDHczqR0C0Qnur1HMQr1ZfZcmrng==, + } + engines: { node: ">=18.0.0" } + hasBin: true + peerDependencies: + publint: ^0.3.0 + typescript: ^5.0.0 + unplugin-lightningcss: ^0.4.0 + unplugin-unused: ^0.5.0 + peerDependenciesMeta: + publint: + optional: true + typescript: + optional: true + unplugin-lightningcss: + optional: true + unplugin-unused: + optional: true + + tslib@2.8.1: + resolution: + { + integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==, + } + + tsx@4.20.3: + resolution: + { + integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==, + } + engines: { node: ">=18.0.0" } + hasBin: true + + type-check@0.4.0: + resolution: + { + integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==, + } + engines: { node: ">= 0.8.0" } + + typescript-eslint@8.34.1: + resolution: + { + integrity: sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: ">=4.8.4 <5.9.0" + + typescript@5.8.3: + resolution: + { + integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==, + } + engines: { node: ">=14.17" } + hasBin: true + + ufo@1.6.1: + resolution: + { + integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==, + } + + unconfig@7.3.2: + resolution: + { + integrity: sha512-nqG5NNL2wFVGZ0NA/aCFw0oJ2pxSf1lwg4Z5ill8wd7K4KX/rQbHlwbh+bjctXL5Ly1xtzHenHGOK0b+lG6JVg==, + } + + undici-types@6.21.0: + resolution: + { + integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==, + } + + unist-util-is@6.0.0: + resolution: + { + integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==, + } + + unist-util-stringify-position@4.0.0: + resolution: + { + integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==, + } + + unist-util-visit-parents@6.0.1: + resolution: + { + integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==, + } + + unist-util-visit@5.0.0: + resolution: + { + integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==, + } + + unrs-resolver@1.9.1: + resolution: + { + integrity: sha512-4AZVxP05JGN6DwqIkSP4VKLOcwQa5l37SWHF/ahcuqBMbfxbpN1L1QKafEhWCziHhzKex9H/AR09H0OuVyU+9g==, + } + + update-browserslist-db@1.1.3: + resolution: + { + integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==, + } + hasBin: true + peerDependencies: + browserslist: ">= 4.21.0" + + uri-js@4.4.1: + resolution: + { + integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, + } + + util-deprecate@1.0.2: + resolution: + { + integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==, + } + + vite-node@3.2.4: + resolution: + { + integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==, + } + engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } + hasBin: true + + vite@6.3.5: + resolution: + { + integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==, + } + engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } + hasBin: true + peerDependencies: + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: ">=1.21.0" + less: "*" + lightningcss: ^1.21.0 + sass: "*" + sass-embedded: "*" + stylus: "*" + sugarss: "*" + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + "@types/node": + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.2.4: + resolution: + { + integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==, + } + engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } + hasBin: true + peerDependencies: + "@edge-runtime/vm": "*" + "@types/debug": ^4.1.12 + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + "@vitest/browser": 3.2.4 + "@vitest/ui": 3.2.4 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@types/debug": + optional: true + "@types/node": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + vue-eslint-parser@10.1.3: + resolution: + { + integrity: sha512-dbCBnd2e02dYWsXoqX5yKUZlOt+ExIpq7hmHKPb5ZqKcjf++Eo0hMseFTZMLKThrUk61m+Uv6A2YSBve6ZvuDQ==, + } + engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + + webidl-conversions@7.0.0: + resolution: + { + integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==, + } + engines: { node: ">=12" } + + whatwg-mimetype@3.0.0: + resolution: + { + integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==, + } + engines: { node: ">=12" } + + which@2.0.2: + resolution: + { + integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==, + } + engines: { node: ">= 8" } + hasBin: true + + why-is-node-running@2.3.0: + resolution: + { + integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==, + } + engines: { node: ">=8" } + hasBin: true + + word-wrap@1.2.5: + resolution: + { + integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==, + } + engines: { node: ">=0.10.0" } + + xml-name-validator@4.0.0: + resolution: + { + integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==, + } + engines: { node: ">=12" } + + yaml-eslint-parser@1.3.0: + resolution: + { + integrity: sha512-E/+VitOorXSLiAqtTd7Yqax0/pAS3xaYMP+AUUJGOK1OZG3rhcj9fcJOM5HJ2VrP1FrStVCWr1muTfQCdj4tAA==, + } + engines: { node: ^14.17.0 || >=16.0.0 } + + yaml@2.8.0: + resolution: + { + integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==, + } + engines: { node: ">= 14.6" } + hasBin: true + + yocto-queue@0.1.0: + resolution: + { + integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==, + } + engines: { node: ">=10" } + + zwitch@2.0.4: + resolution: + { + integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==, + } + +snapshots: + "@babel/generator@7.27.5": + dependencies: + "@babel/parser": 7.27.5 + "@babel/types": 7.27.6 + "@jridgewell/gen-mapping": 0.3.8 + "@jridgewell/trace-mapping": 0.3.25 + jsesc: 3.1.0 + + "@babel/helper-string-parser@7.27.1": {} + + "@babel/helper-validator-identifier@7.27.1": {} + + "@babel/parser@7.27.5": + dependencies: + "@babel/types": 7.27.6 + + "@babel/types@7.27.6": + dependencies: + "@babel/helper-string-parser": 7.27.1 + "@babel/helper-validator-identifier": 7.27.1 + + "@emnapi/core@1.4.3": + dependencies: + "@emnapi/wasi-threads": 1.0.2 + tslib: 2.8.1 + optional: true + + "@emnapi/runtime@1.4.3": + dependencies: + tslib: 2.8.1 + optional: true + + "@emnapi/wasi-threads@1.0.2": + dependencies: + tslib: 2.8.1 + optional: true + + "@es-joy/jsdoccomment@0.50.2": + dependencies: + "@types/estree": 1.0.8 + "@typescript-eslint/types": 8.34.1 + comment-parser: 1.4.1 + esquery: 1.6.0 + jsdoc-type-pratt-parser: 4.1.0 + + "@es-joy/jsdoccomment@0.52.0": + dependencies: + "@types/estree": 1.0.8 + "@typescript-eslint/types": 8.34.1 + comment-parser: 1.4.1 + esquery: 1.6.0 + jsdoc-type-pratt-parser: 4.1.0 + + "@esbuild/aix-ppc64@0.25.5": + optional: true + + "@esbuild/android-arm64@0.25.5": + optional: true + + "@esbuild/android-arm@0.25.5": + optional: true + + "@esbuild/android-x64@0.25.5": + optional: true + + "@esbuild/darwin-arm64@0.25.5": + optional: true + + "@esbuild/darwin-x64@0.25.5": + optional: true + + "@esbuild/freebsd-arm64@0.25.5": + optional: true + + "@esbuild/freebsd-x64@0.25.5": + optional: true + + "@esbuild/linux-arm64@0.25.5": + optional: true + + "@esbuild/linux-arm@0.25.5": + optional: true + + "@esbuild/linux-ia32@0.25.5": + optional: true + + "@esbuild/linux-loong64@0.25.5": + optional: true + + "@esbuild/linux-mips64el@0.25.5": + optional: true + + "@esbuild/linux-ppc64@0.25.5": + optional: true + + "@esbuild/linux-riscv64@0.25.5": + optional: true + + "@esbuild/linux-s390x@0.25.5": + optional: true + + "@esbuild/linux-x64@0.25.5": + optional: true + + "@esbuild/netbsd-arm64@0.25.5": + optional: true + + "@esbuild/netbsd-x64@0.25.5": + optional: true + + "@esbuild/openbsd-arm64@0.25.5": + optional: true + + "@esbuild/openbsd-x64@0.25.5": + optional: true + + "@esbuild/sunos-x64@0.25.5": + optional: true + + "@esbuild/win32-arm64@0.25.5": + optional: true + + "@esbuild/win32-ia32@0.25.5": + optional: true + + "@esbuild/win32-x64@0.25.5": + optional: true + + "@eslint-community/eslint-plugin-eslint-comments@4.5.0(eslint@9.29.0(jiti@2.4.2))": + dependencies: + escape-string-regexp: 4.0.0 + eslint: 9.29.0(jiti@2.4.2) + ignore: 5.3.2 + + "@eslint-community/eslint-utils@4.7.0(eslint@9.29.0(jiti@2.4.2))": + dependencies: + eslint: 9.29.0(jiti@2.4.2) + eslint-visitor-keys: 3.4.3 + + "@eslint-community/regexpp@4.12.1": {} + + "@eslint/compat@1.3.0(eslint@9.29.0(jiti@2.4.2))": + optionalDependencies: + eslint: 9.29.0(jiti@2.4.2) + + "@eslint/config-array@0.20.1": + dependencies: + "@eslint/object-schema": 2.1.6 + debug: 4.4.1 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + "@eslint/config-helpers@0.2.3": {} + + "@eslint/core@0.13.0": + dependencies: + "@types/json-schema": 7.0.15 + + "@eslint/core@0.14.0": + dependencies: + "@types/json-schema": 7.0.15 + + "@eslint/core@0.15.0": + dependencies: + "@types/json-schema": 7.0.15 + + "@eslint/eslintrc@3.3.1": + dependencies: + ajv: 6.12.6 + debug: 4.4.1 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + "@eslint/js@9.29.0": {} + + "@eslint/markdown@6.6.0": + dependencies: + "@eslint/core": 0.14.0 + "@eslint/plugin-kit": 0.3.2 + github-slugger: 2.0.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-frontmatter: 2.0.1 + mdast-util-gfm: 3.1.0 + micromark-extension-frontmatter: 2.0.0 + micromark-extension-gfm: 3.0.0 + transitivePeerDependencies: + - supports-color + + "@eslint/object-schema@2.1.6": {} + + "@eslint/plugin-kit@0.2.8": + dependencies: + "@eslint/core": 0.13.0 + levn: 0.4.1 + + "@eslint/plugin-kit@0.3.2": + dependencies: + "@eslint/core": 0.15.0 + levn: 0.4.1 + + "@humanfs/core@0.19.1": {} + + "@humanfs/node@0.16.6": + dependencies: + "@humanfs/core": 0.19.1 + "@humanwhocodes/retry": 0.3.1 + + "@humanwhocodes/module-importer@1.0.1": {} + + "@humanwhocodes/retry@0.3.1": {} + + "@humanwhocodes/retry@0.4.3": {} + + "@isaacs/balanced-match@4.0.1": {} + + "@isaacs/brace-expansion@5.0.0": + dependencies: + "@isaacs/balanced-match": 4.0.1 + + "@jridgewell/gen-mapping@0.3.8": + dependencies: + "@jridgewell/set-array": 1.2.1 + "@jridgewell/sourcemap-codec": 1.5.0 + "@jridgewell/trace-mapping": 0.3.25 + + "@jridgewell/resolve-uri@3.1.2": {} + + "@jridgewell/set-array@1.2.1": {} + + "@jridgewell/sourcemap-codec@1.5.0": {} + + "@jridgewell/trace-mapping@0.3.25": + dependencies: + "@jridgewell/resolve-uri": 3.1.2 + "@jridgewell/sourcemap-codec": 1.5.0 + + "@napi-rs/wasm-runtime@0.2.11": + dependencies: + "@emnapi/core": 1.4.3 + "@emnapi/runtime": 1.4.3 + "@tybys/wasm-util": 0.9.0 + optional: true + + "@nodelib/fs.scandir@2.1.5": + dependencies: + "@nodelib/fs.stat": 2.0.5 + run-parallel: 1.2.0 + + "@nodelib/fs.stat@2.0.5": {} + + "@nodelib/fs.walk@1.2.8": + dependencies: + "@nodelib/fs.scandir": 2.1.5 + fastq: 1.19.1 + + "@oxc-project/types@0.70.0": {} + + "@pkgr/core@0.2.7": {} + + "@quansync/fs@0.1.3": + dependencies: + quansync: 0.2.10 + + "@rolldown/binding-darwin-arm64@1.0.0-beta.9": + optional: true + + "@rolldown/binding-darwin-x64@1.0.0-beta.9": + optional: true + + "@rolldown/binding-freebsd-x64@1.0.0-beta.9": + optional: true + + "@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9": + optional: true + + "@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9": + optional: true + + "@rolldown/binding-linux-arm64-musl@1.0.0-beta.9": + optional: true + + "@rolldown/binding-linux-x64-gnu@1.0.0-beta.9": + optional: true + + "@rolldown/binding-linux-x64-musl@1.0.0-beta.9": + optional: true + + "@rolldown/binding-wasm32-wasi@1.0.0-beta.9": + dependencies: + "@napi-rs/wasm-runtime": 0.2.11 + optional: true + + "@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9": + optional: true + + "@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9": + optional: true + + "@rolldown/binding-win32-x64-msvc@1.0.0-beta.9": + optional: true + + "@rolldown/pluginutils@1.0.0-beta.9": {} + + "@rollup/rollup-android-arm-eabi@4.44.0": + optional: true + + "@rollup/rollup-android-arm64@4.44.0": + optional: true + + "@rollup/rollup-darwin-arm64@4.44.0": + optional: true + + "@rollup/rollup-darwin-x64@4.44.0": + optional: true + + "@rollup/rollup-freebsd-arm64@4.44.0": + optional: true + + "@rollup/rollup-freebsd-x64@4.44.0": + optional: true + + "@rollup/rollup-linux-arm-gnueabihf@4.44.0": + optional: true + + "@rollup/rollup-linux-arm-musleabihf@4.44.0": + optional: true + + "@rollup/rollup-linux-arm64-gnu@4.44.0": + optional: true + + "@rollup/rollup-linux-arm64-musl@4.44.0": + optional: true + + "@rollup/rollup-linux-loongarch64-gnu@4.44.0": + optional: true + + "@rollup/rollup-linux-powerpc64le-gnu@4.44.0": + optional: true + + "@rollup/rollup-linux-riscv64-gnu@4.44.0": + optional: true + + "@rollup/rollup-linux-riscv64-musl@4.44.0": + optional: true + + "@rollup/rollup-linux-s390x-gnu@4.44.0": + optional: true + + "@rollup/rollup-linux-x64-gnu@4.44.0": + optional: true + + "@rollup/rollup-linux-x64-musl@4.44.0": + optional: true + + "@rollup/rollup-win32-arm64-msvc@4.44.0": + optional: true + + "@rollup/rollup-win32-ia32-msvc@4.44.0": + optional: true + + "@rollup/rollup-win32-x64-msvc@4.44.0": + optional: true + + "@sxzz/eslint-config@7.0.4(@typescript-eslint/eslint-plugin@8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(@typescript-eslint/utils@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)": + dependencies: + "@eslint-community/eslint-plugin-eslint-comments": 4.5.0(eslint@9.29.0(jiti@2.4.2)) + "@eslint/js": 9.29.0 + "@eslint/markdown": 6.6.0 + eslint: 9.29.0(jiti@2.4.2) + eslint-config-flat-gitignore: 2.1.0(eslint@9.29.0(jiti@2.4.2)) + eslint-config-prettier: 10.1.5(eslint@9.29.0(jiti@2.4.2)) + eslint-flat-config-utils: 2.1.0 + eslint-plugin-antfu: 3.1.1(eslint@9.29.0(jiti@2.4.2)) + eslint-plugin-command: 3.3.1(eslint@9.29.0(jiti@2.4.2)) + eslint-plugin-de-morgan: 1.3.0(eslint@9.29.0(jiti@2.4.2)) + eslint-plugin-import-x: 4.15.2(@typescript-eslint/utils@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2)) + eslint-plugin-jsdoc: 51.2.1(eslint@9.29.0(jiti@2.4.2)) + eslint-plugin-jsonc: 2.20.1(eslint@9.29.0(jiti@2.4.2)) + eslint-plugin-n: 17.20.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + eslint-plugin-perfectionist: 4.15.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + eslint-plugin-pnpm: 0.3.1(eslint@9.29.0(jiti@2.4.2)) + eslint-plugin-prettier: 5.5.0(eslint-config-prettier@10.1.5(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2))(prettier@3.6.0) + eslint-plugin-regexp: 2.9.0(eslint@9.29.0(jiti@2.4.2)) + eslint-plugin-sxzz: 0.3.0(eslint@9.29.0(jiti@2.4.2)) + eslint-plugin-unicorn: 59.0.1(eslint@9.29.0(jiti@2.4.2)) + eslint-plugin-unused-imports: 4.1.4(@typescript-eslint/eslint-plugin@8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2)) + eslint-plugin-vue: 10.2.0(eslint@9.29.0(jiti@2.4.2))(vue-eslint-parser@10.1.3(eslint@9.29.0(jiti@2.4.2))) + eslint-plugin-yml: 1.18.0(eslint@9.29.0(jiti@2.4.2)) + globals: 16.2.0 + jsonc-eslint-parser: 2.4.0 + local-pkg: 1.1.1 + prettier: 3.6.0 + typescript-eslint: 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + vue-eslint-parser: 10.1.3(eslint@9.29.0(jiti@2.4.2)) + yaml-eslint-parser: 1.3.0 + transitivePeerDependencies: + - "@eslint/json" + - "@types/eslint" + - "@typescript-eslint/eslint-plugin" + - "@typescript-eslint/utils" + - eslint-import-resolver-node + - supports-color + - typescript + + "@sxzz/prettier-config@2.2.1": {} + + "@tybys/wasm-util@0.9.0": + dependencies: + tslib: 2.8.1 + optional: true + + "@types/chai@5.2.2": + dependencies: + "@types/deep-eql": 4.0.2 + + "@types/debug@4.1.12": + dependencies: + "@types/ms": 2.1.0 + + "@types/deep-eql@4.0.2": {} + + "@types/estree@1.0.8": {} + + "@types/json-schema@7.0.15": {} + + "@types/mdast@4.0.4": + dependencies: + "@types/unist": 3.0.3 + + "@types/ms@2.1.0": {} + + "@types/node@22.15.32": + dependencies: + undici-types: 6.21.0 + + "@types/unist@3.0.3": {} + + "@typescript-eslint/eslint-plugin@8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)": + dependencies: + "@eslint-community/regexpp": 4.12.1 + "@typescript-eslint/parser": 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + "@typescript-eslint/scope-manager": 8.34.1 + "@typescript-eslint/type-utils": 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + "@typescript-eslint/utils": 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + "@typescript-eslint/visitor-keys": 8.34.1 + eslint: 9.29.0(jiti@2.4.2) + graphemer: 1.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)": + dependencies: + "@typescript-eslint/scope-manager": 8.34.1 + "@typescript-eslint/types": 8.34.1 + "@typescript-eslint/typescript-estree": 8.34.1(typescript@5.8.3) + "@typescript-eslint/visitor-keys": 8.34.1 + debug: 4.4.1 + eslint: 9.29.0(jiti@2.4.2) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/project-service@8.34.1(typescript@5.8.3)": + dependencies: + "@typescript-eslint/tsconfig-utils": 8.34.1(typescript@5.8.3) + "@typescript-eslint/types": 8.34.1 + debug: 4.4.1 + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/scope-manager@8.34.1": + dependencies: + "@typescript-eslint/types": 8.34.1 + "@typescript-eslint/visitor-keys": 8.34.1 + + "@typescript-eslint/tsconfig-utils@8.34.1(typescript@5.8.3)": + dependencies: + typescript: 5.8.3 + + "@typescript-eslint/type-utils@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)": + dependencies: + "@typescript-eslint/typescript-estree": 8.34.1(typescript@5.8.3) + "@typescript-eslint/utils": 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + debug: 4.4.1 + eslint: 9.29.0(jiti@2.4.2) + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/types@8.34.1": {} + + "@typescript-eslint/typescript-estree@8.34.1(typescript@5.8.3)": + dependencies: + "@typescript-eslint/project-service": 8.34.1(typescript@5.8.3) + "@typescript-eslint/tsconfig-utils": 8.34.1(typescript@5.8.3) + "@typescript-eslint/types": 8.34.1 + "@typescript-eslint/visitor-keys": 8.34.1 + debug: 4.4.1 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.2 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/utils@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)": + dependencies: + "@eslint-community/eslint-utils": 4.7.0(eslint@9.29.0(jiti@2.4.2)) + "@typescript-eslint/scope-manager": 8.34.1 + "@typescript-eslint/types": 8.34.1 + "@typescript-eslint/typescript-estree": 8.34.1(typescript@5.8.3) + eslint: 9.29.0(jiti@2.4.2) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + "@typescript-eslint/visitor-keys@8.34.1": + dependencies: + "@typescript-eslint/types": 8.34.1 + eslint-visitor-keys: 4.2.1 + + "@unrs/resolver-binding-android-arm-eabi@1.9.1": + optional: true + + "@unrs/resolver-binding-android-arm64@1.9.1": + optional: true + + "@unrs/resolver-binding-darwin-arm64@1.9.1": + optional: true + + "@unrs/resolver-binding-darwin-x64@1.9.1": + optional: true + + "@unrs/resolver-binding-freebsd-x64@1.9.1": + optional: true + + "@unrs/resolver-binding-linux-arm-gnueabihf@1.9.1": + optional: true + + "@unrs/resolver-binding-linux-arm-musleabihf@1.9.1": + optional: true + + "@unrs/resolver-binding-linux-arm64-gnu@1.9.1": + optional: true + + "@unrs/resolver-binding-linux-arm64-musl@1.9.1": + optional: true + + "@unrs/resolver-binding-linux-ppc64-gnu@1.9.1": + optional: true + + "@unrs/resolver-binding-linux-riscv64-gnu@1.9.1": + optional: true + + "@unrs/resolver-binding-linux-riscv64-musl@1.9.1": + optional: true + + "@unrs/resolver-binding-linux-s390x-gnu@1.9.1": + optional: true + + "@unrs/resolver-binding-linux-x64-gnu@1.9.1": + optional: true + + "@unrs/resolver-binding-linux-x64-musl@1.9.1": + optional: true + + "@unrs/resolver-binding-wasm32-wasi@1.9.1": + dependencies: + "@napi-rs/wasm-runtime": 0.2.11 + optional: true + + "@unrs/resolver-binding-win32-arm64-msvc@1.9.1": + optional: true + + "@unrs/resolver-binding-win32-ia32-msvc@1.9.1": + optional: true + + "@unrs/resolver-binding-win32-x64-msvc@1.9.1": + optional: true + + "@vitest/expect@3.2.4": + dependencies: + "@types/chai": 5.2.2 + "@vitest/spy": 3.2.4 + "@vitest/utils": 3.2.4 + chai: 5.2.0 + tinyrainbow: 2.0.0 + + "@vitest/mocker@3.2.4(vite@6.3.5(@types/node@22.15.32)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0))": + dependencies: + "@vitest/spy": 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 6.3.5(@types/node@22.15.32)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + + "@vitest/pretty-format@3.2.4": + dependencies: + tinyrainbow: 2.0.0 + + "@vitest/runner@3.2.4": + dependencies: + "@vitest/utils": 3.2.4 + pathe: 2.0.3 + strip-literal: 3.0.0 + + "@vitest/snapshot@3.2.4": + dependencies: + "@vitest/pretty-format": 3.2.4 + magic-string: 0.30.17 + pathe: 2.0.3 + + "@vitest/spy@3.2.4": + dependencies: + tinyspy: 4.0.3 + + "@vitest/utils@3.2.4": + dependencies: + "@vitest/pretty-format": 3.2.4 + loupe: 3.1.4 + tinyrainbow: 2.0.0 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansis@4.1.0: {} + + are-docs-informative@0.0.2: {} + + argparse@2.0.1: {} + + args-tokenizer@0.3.0: {} + + assertion-error@2.0.1: {} + + ast-kit@2.1.0: + dependencies: + "@babel/parser": 7.27.5 + pathe: 2.0.3 + + balanced-match@1.0.2: {} + + birpc@2.4.0: {} + + boolbase@1.0.0: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.25.0: + dependencies: + caniuse-lite: 1.0.30001724 + electron-to-chromium: 1.5.171 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.25.0) + + builtin-modules@5.0.0: {} + + bumpp@10.2.0: + dependencies: + ansis: 4.1.0 + args-tokenizer: 0.3.0 + c12: 3.0.4 + cac: 6.7.14 + escalade: 3.2.0 + jsonc-parser: 3.3.1 + package-manager-detector: 1.3.0 + semver: 7.7.2 + tinyexec: 1.0.1 + tinyglobby: 0.2.14 + yaml: 2.8.0 + transitivePeerDependencies: + - magicast + + c12@3.0.4: + dependencies: + chokidar: 4.0.3 + confbox: 0.2.2 + defu: 6.1.4 + dotenv: 16.5.0 + exsolve: 1.0.7 + giget: 2.0.0 + jiti: 2.4.2 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 1.0.0 + pkg-types: 2.1.0 + rc9: 2.1.2 + + cac@6.7.14: {} + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001724: {} + + ccount@2.0.1: {} + + chai@5.2.0: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.4 + pathval: 2.0.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + character-entities@2.0.2: {} + + check-error@2.1.1: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + ci-info@4.2.0: {} + + citty@0.1.6: + dependencies: + consola: 3.4.2 + + clean-regexp@1.0.0: + dependencies: + escape-string-regexp: 1.0.5 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + comment-parser@1.4.1: {} + + concat-map@0.0.1: {} + + confbox@0.1.8: {} + + confbox@0.2.2: {} + + consola@3.4.2: {} + + core-js-compat@3.43.0: + dependencies: + browserslist: 4.25.0 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.2.0: + dependencies: + character-entities: 2.0.2 + + deep-eql@5.0.2: {} + + deep-is@0.1.4: {} + + defu@6.1.4: {} + + dequal@2.0.3: {} + + destr@2.0.5: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + diff@8.0.2: {} + + dotenv@16.5.0: {} + + dts-resolver@2.1.1: {} + + electron-to-chromium@1.5.171: {} + + empathic@1.1.0: {} + + enhanced-resolve@5.18.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.2 + + es-module-lexer@1.7.0: {} + + esbuild@0.25.5: + optionalDependencies: + "@esbuild/aix-ppc64": 0.25.5 + "@esbuild/android-arm": 0.25.5 + "@esbuild/android-arm64": 0.25.5 + "@esbuild/android-x64": 0.25.5 + "@esbuild/darwin-arm64": 0.25.5 + "@esbuild/darwin-x64": 0.25.5 + "@esbuild/freebsd-arm64": 0.25.5 + "@esbuild/freebsd-x64": 0.25.5 + "@esbuild/linux-arm": 0.25.5 + "@esbuild/linux-arm64": 0.25.5 + "@esbuild/linux-ia32": 0.25.5 + "@esbuild/linux-loong64": 0.25.5 + "@esbuild/linux-mips64el": 0.25.5 + "@esbuild/linux-ppc64": 0.25.5 + "@esbuild/linux-riscv64": 0.25.5 + "@esbuild/linux-s390x": 0.25.5 + "@esbuild/linux-x64": 0.25.5 + "@esbuild/netbsd-arm64": 0.25.5 + "@esbuild/netbsd-x64": 0.25.5 + "@esbuild/openbsd-arm64": 0.25.5 + "@esbuild/openbsd-x64": 0.25.5 + "@esbuild/sunos-x64": 0.25.5 + "@esbuild/win32-arm64": 0.25.5 + "@esbuild/win32-ia32": 0.25.5 + "@esbuild/win32-x64": 0.25.5 + + escalade@3.2.0: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + escape-string-regexp@5.0.0: {} + + eslint-compat-utils@0.5.1(eslint@9.29.0(jiti@2.4.2)): + dependencies: + eslint: 9.29.0(jiti@2.4.2) + semver: 7.7.2 + + eslint-compat-utils@0.6.5(eslint@9.29.0(jiti@2.4.2)): + dependencies: + eslint: 9.29.0(jiti@2.4.2) + semver: 7.7.2 + + eslint-config-flat-gitignore@2.1.0(eslint@9.29.0(jiti@2.4.2)): + dependencies: + "@eslint/compat": 1.3.0(eslint@9.29.0(jiti@2.4.2)) + eslint: 9.29.0(jiti@2.4.2) + + eslint-config-prettier@10.1.5(eslint@9.29.0(jiti@2.4.2)): + dependencies: + eslint: 9.29.0(jiti@2.4.2) + + eslint-flat-config-utils@2.1.0: + dependencies: + pathe: 2.0.3 + + eslint-import-context@0.1.8(unrs-resolver@1.9.1): + dependencies: + get-tsconfig: 4.10.1 + stable-hash-x: 0.1.1 + optionalDependencies: + unrs-resolver: 1.9.1 + + eslint-json-compat-utils@0.2.1(eslint@9.29.0(jiti@2.4.2))(jsonc-eslint-parser@2.4.0): + dependencies: + eslint: 9.29.0(jiti@2.4.2) + esquery: 1.6.0 + jsonc-eslint-parser: 2.4.0 + + eslint-plugin-antfu@3.1.1(eslint@9.29.0(jiti@2.4.2)): + dependencies: + eslint: 9.29.0(jiti@2.4.2) + + eslint-plugin-command@3.3.1(eslint@9.29.0(jiti@2.4.2)): + dependencies: + "@es-joy/jsdoccomment": 0.50.2 + eslint: 9.29.0(jiti@2.4.2) + + eslint-plugin-de-morgan@1.3.0(eslint@9.29.0(jiti@2.4.2)): + dependencies: + eslint: 9.29.0(jiti@2.4.2) + + eslint-plugin-es-x@7.8.0(eslint@9.29.0(jiti@2.4.2)): + dependencies: + "@eslint-community/eslint-utils": 4.7.0(eslint@9.29.0(jiti@2.4.2)) + "@eslint-community/regexpp": 4.12.1 + eslint: 9.29.0(jiti@2.4.2) + eslint-compat-utils: 0.5.1(eslint@9.29.0(jiti@2.4.2)) + + eslint-plugin-import-x@4.15.2(@typescript-eslint/utils@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2)): + dependencies: + "@typescript-eslint/types": 8.34.1 + comment-parser: 1.4.1 + debug: 4.4.1 + eslint: 9.29.0(jiti@2.4.2) + eslint-import-context: 0.1.8(unrs-resolver@1.9.1) + is-glob: 4.0.3 + minimatch: 10.0.3 + semver: 7.7.2 + stable-hash-x: 0.1.1 + unrs-resolver: 1.9.1 + optionalDependencies: + "@typescript-eslint/utils": 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + transitivePeerDependencies: + - supports-color + + eslint-plugin-jsdoc@51.2.1(eslint@9.29.0(jiti@2.4.2)): + dependencies: + "@es-joy/jsdoccomment": 0.52.0 + are-docs-informative: 0.0.2 + comment-parser: 1.4.1 + debug: 4.4.1 + escape-string-regexp: 4.0.0 + eslint: 9.29.0(jiti@2.4.2) + espree: 10.4.0 + esquery: 1.6.0 + parse-imports-exports: 0.2.4 + semver: 7.7.2 + spdx-expression-parse: 4.0.0 + transitivePeerDependencies: + - supports-color + + eslint-plugin-jsonc@2.20.1(eslint@9.29.0(jiti@2.4.2)): + dependencies: + "@eslint-community/eslint-utils": 4.7.0(eslint@9.29.0(jiti@2.4.2)) + eslint: 9.29.0(jiti@2.4.2) + eslint-compat-utils: 0.6.5(eslint@9.29.0(jiti@2.4.2)) + eslint-json-compat-utils: 0.2.1(eslint@9.29.0(jiti@2.4.2))(jsonc-eslint-parser@2.4.0) + espree: 10.4.0 + graphemer: 1.4.0 + jsonc-eslint-parser: 2.4.0 + natural-compare: 1.4.0 + synckit: 0.11.8 + transitivePeerDependencies: + - "@eslint/json" + + eslint-plugin-n@17.20.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3): + dependencies: + "@eslint-community/eslint-utils": 4.7.0(eslint@9.29.0(jiti@2.4.2)) + "@typescript-eslint/utils": 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + enhanced-resolve: 5.18.1 + eslint: 9.29.0(jiti@2.4.2) + eslint-plugin-es-x: 7.8.0(eslint@9.29.0(jiti@2.4.2)) + get-tsconfig: 4.10.1 + globals: 15.15.0 + ignore: 5.3.2 + minimatch: 9.0.5 + semver: 7.7.2 + ts-declaration-location: 1.0.7(typescript@5.8.3) + transitivePeerDependencies: + - supports-color + - typescript + + eslint-plugin-perfectionist@4.15.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3): + dependencies: + "@typescript-eslint/types": 8.34.1 + "@typescript-eslint/utils": 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.29.0(jiti@2.4.2) + natural-orderby: 5.0.0 + transitivePeerDependencies: + - supports-color + - typescript + + eslint-plugin-pnpm@0.3.1(eslint@9.29.0(jiti@2.4.2)): + dependencies: + eslint: 9.29.0(jiti@2.4.2) + find-up-simple: 1.0.1 + jsonc-eslint-parser: 2.4.0 + pathe: 2.0.3 + pnpm-workspace-yaml: 0.3.1 + tinyglobby: 0.2.14 + yaml-eslint-parser: 1.3.0 + + eslint-plugin-prettier@5.5.0(eslint-config-prettier@10.1.5(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2))(prettier@3.6.0): + dependencies: + eslint: 9.29.0(jiti@2.4.2) + prettier: 3.6.0 + prettier-linter-helpers: 1.0.0 + synckit: 0.11.8 + optionalDependencies: + eslint-config-prettier: 10.1.5(eslint@9.29.0(jiti@2.4.2)) + + eslint-plugin-regexp@2.9.0(eslint@9.29.0(jiti@2.4.2)): + dependencies: + "@eslint-community/eslint-utils": 4.7.0(eslint@9.29.0(jiti@2.4.2)) + "@eslint-community/regexpp": 4.12.1 + comment-parser: 1.4.1 + eslint: 9.29.0(jiti@2.4.2) + jsdoc-type-pratt-parser: 4.1.0 + refa: 0.12.1 + regexp-ast-analysis: 0.7.1 + scslre: 0.3.0 + + eslint-plugin-sxzz@0.3.0(eslint@9.29.0(jiti@2.4.2)): + dependencies: + eslint: 9.29.0(jiti@2.4.2) + + eslint-plugin-unicorn@59.0.1(eslint@9.29.0(jiti@2.4.2)): + dependencies: + "@babel/helper-validator-identifier": 7.27.1 + "@eslint-community/eslint-utils": 4.7.0(eslint@9.29.0(jiti@2.4.2)) + "@eslint/plugin-kit": 0.2.8 + ci-info: 4.2.0 + clean-regexp: 1.0.0 + core-js-compat: 3.43.0 + eslint: 9.29.0(jiti@2.4.2) + esquery: 1.6.0 + find-up-simple: 1.0.1 + globals: 16.2.0 + indent-string: 5.0.0 + is-builtin-module: 5.0.0 + jsesc: 3.1.0 + pluralize: 8.0.0 + regexp-tree: 0.1.27 + regjsparser: 0.12.0 + semver: 7.7.2 + strip-indent: 4.0.0 + + eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2)): + dependencies: + eslint: 9.29.0(jiti@2.4.2) + optionalDependencies: + "@typescript-eslint/eslint-plugin": 8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + + eslint-plugin-vue@10.2.0(eslint@9.29.0(jiti@2.4.2))(vue-eslint-parser@10.1.3(eslint@9.29.0(jiti@2.4.2))): + dependencies: + "@eslint-community/eslint-utils": 4.7.0(eslint@9.29.0(jiti@2.4.2)) + eslint: 9.29.0(jiti@2.4.2) + natural-compare: 1.4.0 + nth-check: 2.1.1 + postcss-selector-parser: 6.1.2 + semver: 7.7.2 + vue-eslint-parser: 10.1.3(eslint@9.29.0(jiti@2.4.2)) + xml-name-validator: 4.0.0 + + eslint-plugin-yml@1.18.0(eslint@9.29.0(jiti@2.4.2)): + dependencies: + debug: 4.4.1 + escape-string-regexp: 4.0.0 + eslint: 9.29.0(jiti@2.4.2) + eslint-compat-utils: 0.6.5(eslint@9.29.0(jiti@2.4.2)) + natural-compare: 1.4.0 + yaml-eslint-parser: 1.3.0 + transitivePeerDependencies: + - supports-color + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.29.0(jiti@2.4.2): + dependencies: + "@eslint-community/eslint-utils": 4.7.0(eslint@9.29.0(jiti@2.4.2)) + "@eslint-community/regexpp": 4.12.1 + "@eslint/config-array": 0.20.1 + "@eslint/config-helpers": 0.2.3 + "@eslint/core": 0.14.0 + "@eslint/eslintrc": 3.3.1 + "@eslint/js": 9.29.0 + "@eslint/plugin-kit": 0.3.2 + "@humanfs/node": 0.16.6 + "@humanwhocodes/module-importer": 1.0.1 + "@humanwhocodes/retry": 0.4.3 + "@types/estree": 1.0.8 + "@types/json-schema": 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.1 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.4.2 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + espree@9.6.1: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 3.4.3 + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@3.0.3: + dependencies: + "@types/estree": 1.0.8 + + esutils@2.0.3: {} + + expect-type@1.2.1: {} + + exsolve@1.0.7: {} + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-glob@3.3.3: + dependencies: + "@nodelib/fs.stat": 2.0.5 + "@nodelib/fs.walk": 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fault@2.0.1: + dependencies: + format: 0.2.2 + + fdir@6.4.6(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up-simple@1.0.1: {} + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + format@0.2.2: {} + + fsevents@2.3.3: + optional: true + + get-tsconfig@4.10.1: + dependencies: + resolve-pkg-maps: 1.0.0 + + giget@2.0.0: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + defu: 6.1.4 + node-fetch-native: 1.6.6 + nypm: 0.6.0 + pathe: 2.0.3 + + github-slugger@2.0.0: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + globals@15.15.0: {} + + globals@16.2.0: {} + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + happy-dom@17.6.3: + dependencies: + webidl-conversions: 7.0.0 + whatwg-mimetype: 3.0.0 + + has-flag@4.0.0: {} + + hookable@5.5.3: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + indent-string@5.0.0: {} + + is-builtin-module@5.0.0: + dependencies: + builtin-modules: 5.0.0 + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + isexe@2.0.0: {} + + jiti@2.4.2: {} + + js-tokens@9.0.1: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsdoc-type-pratt-parser@4.1.0: {} + + jsesc@3.0.2: {} + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + jsonc-eslint-parser@2.4.0: + dependencies: + acorn: 8.15.0 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + semver: 7.7.2 + + jsonc-parser@3.3.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + local-pkg@1.1.1: + dependencies: + mlly: 1.7.4 + pkg-types: 2.1.0 + quansync: 0.2.10 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + lodash@4.17.21: {} + + longest-streak@3.1.0: {} + + loupe@3.1.4: {} + + magic-string@0.30.17: + dependencies: + "@jridgewell/sourcemap-codec": 1.5.0 + + markdown-table@3.0.4: {} + + mdast-util-find-and-replace@3.0.2: + dependencies: + "@types/mdast": 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + mdast-util-from-markdown@2.0.2: + dependencies: + "@types/mdast": 4.0.4 + "@types/unist": 3.0.3 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-frontmatter@2.0.1: + dependencies: + "@types/mdast": 4.0.4 + devlop: 1.1.0 + escape-string-regexp: 5.0.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-extension-frontmatter: 2.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + "@types/mdast": 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + "@types/mdast": 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + "@types/mdast": 4.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + "@types/mdast": 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + "@types/mdast": 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + "@types/mdast": 4.0.4 + unist-util-is: 6.0.0 + + mdast-util-to-markdown@2.1.2: + dependencies: + "@types/mdast": 4.0.4 + "@types/unist": 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + "@types/mdast": 4.0.4 + + merge2@1.4.1: {} + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-frontmatter@2.0.0: + dependencies: + fault: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.2.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + "@types/debug": 4.1.12 + debug: 4.4.1 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + min-indent@1.0.1: {} + + minimatch@10.0.3: + dependencies: + "@isaacs/brace-expansion": 5.0.0 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + mlly@1.7.4: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + napi-postinstall@0.2.4: {} + + natural-compare@1.4.0: {} + + natural-orderby@5.0.0: {} + + node-fetch-native@1.6.6: {} + + node-releases@2.0.19: {} + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + nypm@0.6.0: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + pathe: 2.0.3 + pkg-types: 2.1.0 + tinyexec: 0.3.2 + + ohash@2.0.11: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-manager-detector@1.3.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-imports-exports@0.2.4: + dependencies: + parse-statements: 1.0.11 + + parse-statements@1.0.11: {} + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + pathe@2.0.3: {} + + pathval@2.0.0: {} + + perfect-debounce@1.0.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.2: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.7.4 + pathe: 2.0.3 + + pkg-types@2.1.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.7 + pathe: 2.0.3 + + pluralize@8.0.0: {} + + pnpm-workspace-yaml@0.3.1: + dependencies: + yaml: 2.8.0 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + + prettier@3.6.0: {} + + punycode@2.3.1: {} + + quansync@0.2.10: {} + + queue-microtask@1.2.3: {} + + rc9@2.1.2: + dependencies: + defu: 6.1.4 + destr: 2.0.5 + + readdirp@4.1.2: {} + + refa@0.12.1: + dependencies: + "@eslint-community/regexpp": 4.12.1 + + regexp-ast-analysis@0.7.1: + dependencies: + "@eslint-community/regexpp": 4.12.1 + refa: 0.12.1 + + regexp-tree@0.1.27: {} + + regjsparser@0.12.0: + dependencies: + jsesc: 3.0.2 + + resolve-from@4.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + reusify@1.1.0: {} + + rolldown-plugin-dts@0.13.12(rolldown@1.0.0-beta.9)(typescript@5.8.3): + dependencies: + "@babel/generator": 7.27.5 + "@babel/parser": 7.27.5 + "@babel/types": 7.27.6 + ast-kit: 2.1.0 + birpc: 2.4.0 + debug: 4.4.1 + dts-resolver: 2.1.1 + get-tsconfig: 4.10.1 + rolldown: 1.0.0-beta.9 + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - oxc-resolver + - supports-color + + rolldown@1.0.0-beta.9: + dependencies: + "@oxc-project/types": 0.70.0 + "@rolldown/pluginutils": 1.0.0-beta.9 + ansis: 4.1.0 + optionalDependencies: + "@rolldown/binding-darwin-arm64": 1.0.0-beta.9 + "@rolldown/binding-darwin-x64": 1.0.0-beta.9 + "@rolldown/binding-freebsd-x64": 1.0.0-beta.9 + "@rolldown/binding-linux-arm-gnueabihf": 1.0.0-beta.9 + "@rolldown/binding-linux-arm64-gnu": 1.0.0-beta.9 + "@rolldown/binding-linux-arm64-musl": 1.0.0-beta.9 + "@rolldown/binding-linux-x64-gnu": 1.0.0-beta.9 + "@rolldown/binding-linux-x64-musl": 1.0.0-beta.9 + "@rolldown/binding-wasm32-wasi": 1.0.0-beta.9 + "@rolldown/binding-win32-arm64-msvc": 1.0.0-beta.9 + "@rolldown/binding-win32-ia32-msvc": 1.0.0-beta.9 + "@rolldown/binding-win32-x64-msvc": 1.0.0-beta.9 + + rollup@4.44.0: + dependencies: + "@types/estree": 1.0.8 + optionalDependencies: + "@rollup/rollup-android-arm-eabi": 4.44.0 + "@rollup/rollup-android-arm64": 4.44.0 + "@rollup/rollup-darwin-arm64": 4.44.0 + "@rollup/rollup-darwin-x64": 4.44.0 + "@rollup/rollup-freebsd-arm64": 4.44.0 + "@rollup/rollup-freebsd-x64": 4.44.0 + "@rollup/rollup-linux-arm-gnueabihf": 4.44.0 + "@rollup/rollup-linux-arm-musleabihf": 4.44.0 + "@rollup/rollup-linux-arm64-gnu": 4.44.0 + "@rollup/rollup-linux-arm64-musl": 4.44.0 + "@rollup/rollup-linux-loongarch64-gnu": 4.44.0 + "@rollup/rollup-linux-powerpc64le-gnu": 4.44.0 + "@rollup/rollup-linux-riscv64-gnu": 4.44.0 + "@rollup/rollup-linux-riscv64-musl": 4.44.0 + "@rollup/rollup-linux-s390x-gnu": 4.44.0 + "@rollup/rollup-linux-x64-gnu": 4.44.0 + "@rollup/rollup-linux-x64-musl": 4.44.0 + "@rollup/rollup-win32-arm64-msvc": 4.44.0 + "@rollup/rollup-win32-ia32-msvc": 4.44.0 + "@rollup/rollup-win32-x64-msvc": 4.44.0 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + scslre@0.3.0: + dependencies: + "@eslint-community/regexpp": 4.12.1 + refa: 0.12.1 + regexp-ast-analysis: 0.7.1 + + semver@7.7.2: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + + source-map-js@1.2.1: {} + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@4.0.0: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.21 + + spdx-license-ids@3.0.21: {} + + stable-hash-x@0.1.1: {} + + stackback@0.0.2: {} + + std-env@3.9.0: {} + + strip-indent@4.0.0: + dependencies: + min-indent: 1.0.1 + + strip-json-comments@3.1.1: {} + + strip-literal@3.0.0: + dependencies: + js-tokens: 9.0.1 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + synckit@0.11.8: + dependencies: + "@pkgr/core": 0.2.7 + + tapable@2.2.2: {} + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyexec@1.0.1: {} + + tinyglobby@0.2.14: + dependencies: + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 + + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.3: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + ts-api-utils@2.1.0(typescript@5.8.3): + dependencies: + typescript: 5.8.3 + + ts-declaration-location@1.0.7(typescript@5.8.3): + dependencies: + picomatch: 4.0.2 + typescript: 5.8.3 + + tsdown@0.11.13(typescript@5.8.3): + dependencies: + ansis: 4.1.0 + cac: 6.7.14 + chokidar: 4.0.3 + debug: 4.4.1 + diff: 8.0.2 + empathic: 1.1.0 + hookable: 5.5.3 + rolldown: 1.0.0-beta.9 + rolldown-plugin-dts: 0.13.12(rolldown@1.0.0-beta.9)(typescript@5.8.3) + semver: 7.7.2 + tinyexec: 1.0.1 + tinyglobby: 0.2.14 + unconfig: 7.3.2 + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - "@oxc-project/runtime" + - "@typescript/native-preview" + - oxc-resolver + - supports-color + - vue-tsc + + tslib@2.8.1: + optional: true + + tsx@4.20.3: + dependencies: + esbuild: 0.25.5 + get-tsconfig: 4.10.1 + optionalDependencies: + fsevents: 2.3.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3): + dependencies: + "@typescript-eslint/eslint-plugin": 8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + "@typescript-eslint/parser": 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + "@typescript-eslint/utils": 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.29.0(jiti@2.4.2) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + typescript@5.8.3: {} + + ufo@1.6.1: {} + + unconfig@7.3.2: + dependencies: + "@quansync/fs": 0.1.3 + defu: 6.1.4 + jiti: 2.4.2 + quansync: 0.2.10 + + undici-types@6.21.0: {} + + unist-util-is@6.0.0: + dependencies: + "@types/unist": 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + "@types/unist": 3.0.3 + + unist-util-visit-parents@6.0.1: + dependencies: + "@types/unist": 3.0.3 + unist-util-is: 6.0.0 + + unist-util-visit@5.0.0: + dependencies: + "@types/unist": 3.0.3 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + unrs-resolver@1.9.1: + dependencies: + napi-postinstall: 0.2.4 + optionalDependencies: + "@unrs/resolver-binding-android-arm-eabi": 1.9.1 + "@unrs/resolver-binding-android-arm64": 1.9.1 + "@unrs/resolver-binding-darwin-arm64": 1.9.1 + "@unrs/resolver-binding-darwin-x64": 1.9.1 + "@unrs/resolver-binding-freebsd-x64": 1.9.1 + "@unrs/resolver-binding-linux-arm-gnueabihf": 1.9.1 + "@unrs/resolver-binding-linux-arm-musleabihf": 1.9.1 + "@unrs/resolver-binding-linux-arm64-gnu": 1.9.1 + "@unrs/resolver-binding-linux-arm64-musl": 1.9.1 + "@unrs/resolver-binding-linux-ppc64-gnu": 1.9.1 + "@unrs/resolver-binding-linux-riscv64-gnu": 1.9.1 + "@unrs/resolver-binding-linux-riscv64-musl": 1.9.1 + "@unrs/resolver-binding-linux-s390x-gnu": 1.9.1 + "@unrs/resolver-binding-linux-x64-gnu": 1.9.1 + "@unrs/resolver-binding-linux-x64-musl": 1.9.1 + "@unrs/resolver-binding-wasm32-wasi": 1.9.1 + "@unrs/resolver-binding-win32-arm64-msvc": 1.9.1 + "@unrs/resolver-binding-win32-ia32-msvc": 1.9.1 + "@unrs/resolver-binding-win32-x64-msvc": 1.9.1 + + update-browserslist-db@1.1.3(browserslist@4.25.0): + dependencies: + browserslist: 4.25.0 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + vite-node@3.2.4(@types/node@22.15.32)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0): + dependencies: + cac: 6.7.14 + debug: 4.4.1 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 6.3.5(@types/node@22.15.32)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + transitivePeerDependencies: + - "@types/node" + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite@6.3.5(@types/node@22.15.32)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0): + dependencies: + esbuild: 0.25.5 + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 + postcss: 8.5.6 + rollup: 4.44.0 + tinyglobby: 0.2.14 + optionalDependencies: + "@types/node": 22.15.32 + fsevents: 2.3.3 + jiti: 2.4.2 + tsx: 4.20.3 + yaml: 2.8.0 + + vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.32)(happy-dom@17.6.3)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0): + dependencies: + "@types/chai": 5.2.2 + "@vitest/expect": 3.2.4 + "@vitest/mocker": 3.2.4(vite@6.3.5(@types/node@22.15.32)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0)) + "@vitest/pretty-format": 3.2.4 + "@vitest/runner": 3.2.4 + "@vitest/snapshot": 3.2.4 + "@vitest/spy": 3.2.4 + "@vitest/utils": 3.2.4 + chai: 5.2.0 + debug: 4.4.1 + expect-type: 1.2.1 + magic-string: 0.30.17 + pathe: 2.0.3 + picomatch: 4.0.2 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.14 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 6.3.5(@types/node@22.15.32)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + vite-node: 3.2.4(@types/node@22.15.32)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + why-is-node-running: 2.3.0 + optionalDependencies: + "@types/debug": 4.1.12 + "@types/node": 22.15.32 + happy-dom: 17.6.3 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vue-eslint-parser@10.1.3(eslint@9.29.0(jiti@2.4.2)): + dependencies: + debug: 4.4.1 + eslint: 9.29.0(jiti@2.4.2) + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + lodash: 4.17.21 + semver: 7.7.2 + transitivePeerDependencies: + - supports-color + + webidl-conversions@7.0.0: {} + + whatwg-mimetype@3.0.0: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + xml-name-validator@4.0.0: {} + + yaml-eslint-parser@1.3.0: + dependencies: + eslint-visitor-keys: 3.4.3 + yaml: 2.8.0 + + yaml@2.8.0: {} + + yocto-queue@0.1.0: {} + + zwitch@2.0.4: {} diff --git a/libs/typescript/core/src/index.ts b/libs/typescript/core/src/index.ts new file mode 100644 index 00000000..da86d36e --- /dev/null +++ b/libs/typescript/core/src/index.ts @@ -0,0 +1,3 @@ +export const myFunction = () => { + return 'Hello, world!' +} diff --git a/libs/typescript/core/tests/index.test.ts b/libs/typescript/core/tests/index.test.ts new file mode 100644 index 00000000..3ef6cded --- /dev/null +++ b/libs/typescript/core/tests/index.test.ts @@ -0,0 +1,6 @@ +import { expect, test } from 'vitest' +import { myFunction } from '../src' + +test('myFunction', () => { + expect(myFunction()).toBe('Hello, world!') +}) diff --git a/libs/typescript/core/tsconfig.json b/libs/typescript/core/tsconfig.json new file mode 100644 index 00000000..77b838a8 --- /dev/null +++ b/libs/typescript/core/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": ["es2023"], + "moduleDetection": "force", + "module": "preserve", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "types": ["node"], + "strict": true, + "noUnusedLocals": true, + "declaration": true, + "emitDeclarationOnly": true, + "esModuleInterop": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src"] +} diff --git a/libs/typescript/core/tsdown.config.ts b/libs/typescript/core/tsdown.config.ts new file mode 100644 index 00000000..f30bbedd --- /dev/null +++ b/libs/typescript/core/tsdown.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'tsdown' + +export default defineConfig([ + { + entry: ['./src/index.ts'], + platform: 'neutral', + dts: true, + }, +]) diff --git a/libs/typescript/core/vitest.config.ts b/libs/typescript/core/vitest.config.ts new file mode 100644 index 00000000..abed6b21 --- /dev/null +++ b/libs/typescript/core/vitest.config.ts @@ -0,0 +1,3 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({}) From adbf3f378cdf8f0ef323689e53f137ba3631643a Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Mon, 23 Jun 2025 16:05:47 -0700 Subject: [PATCH 098/141] Organize ts libs, add global workspace --- .vscode/computer-ts.code-workspace | 13 - ....code-workspace => libs-ts.code-workspace} | 4 +- libs/typescript/.gitignore | 5 + libs/typescript/{computer => }/biome.json | 0 libs/typescript/computer/package.json | 1 + libs/typescript/computer/pnpm-lock.yaml | 3 + libs/typescript/computer/pnpm-workspace.yaml | 3 - .../computer/src/computer/providers/base.ts | 9 + libs/typescript/core/biome.json | 86 - libs/typescript/core/package.json | 5 +- libs/typescript/core/pnpm-lock.yaml | 5501 +++-------------- libs/typescript/core/src/index.ts | 10 +- .../core/src/telemetry/clients/index.ts | 1 + .../core/src/telemetry/clients/posthog.ts | 309 + libs/typescript/core/src/telemetry/index.ts | 7 + libs/typescript/core/tests/index.test.ts | 6 - libs/typescript/core/tests/telemetry.test.ts | 30 + libs/typescript/package.json | 21 + libs/typescript/pnpm-lock.yaml | 105 + 19 files changed, 1324 insertions(+), 4795 deletions(-) delete mode 100644 .vscode/computer-ts.code-workspace rename .vscode/{core-ts.code-workspace => libs-ts.code-workspace} (63%) create mode 100644 libs/typescript/.gitignore rename libs/typescript/{computer => }/biome.json (100%) delete mode 100644 libs/typescript/computer/pnpm-workspace.yaml delete mode 100644 libs/typescript/core/biome.json create mode 100644 libs/typescript/core/src/telemetry/clients/index.ts create mode 100644 libs/typescript/core/src/telemetry/clients/posthog.ts create mode 100644 libs/typescript/core/src/telemetry/index.ts delete mode 100644 libs/typescript/core/tests/index.test.ts create mode 100644 libs/typescript/core/tests/telemetry.test.ts create mode 100644 libs/typescript/package.json create mode 100644 libs/typescript/pnpm-lock.yaml diff --git a/.vscode/computer-ts.code-workspace b/.vscode/computer-ts.code-workspace deleted file mode 100644 index 1c74b456..00000000 --- a/.vscode/computer-ts.code-workspace +++ /dev/null @@ -1,13 +0,0 @@ -{ - "folders": [ - { - "name": "computer-ts", - "path": "../libs/typescript/computer" - } - ], - "extensions": { - "recommendations": [ - "biomejs.biome", - ] - } -} \ No newline at end of file diff --git a/.vscode/core-ts.code-workspace b/.vscode/libs-ts.code-workspace similarity index 63% rename from .vscode/core-ts.code-workspace rename to .vscode/libs-ts.code-workspace index 3da96d33..732316f2 100644 --- a/.vscode/core-ts.code-workspace +++ b/.vscode/libs-ts.code-workspace @@ -1,8 +1,8 @@ { "folders": [ { - "name": "core-ts", - "path": "../libs/typescript/core" + "name": "libs-ts", + "path": "../libs/typescript" } ], "extensions": { diff --git a/libs/typescript/.gitignore b/libs/typescript/.gitignore new file mode 100644 index 00000000..77dae5e3 --- /dev/null +++ b/libs/typescript/.gitignore @@ -0,0 +1,5 @@ +node_modules + +*.log +.DS_Store +.eslintcache diff --git a/libs/typescript/computer/biome.json b/libs/typescript/biome.json similarity index 100% rename from libs/typescript/computer/biome.json rename to libs/typescript/biome.json diff --git a/libs/typescript/computer/package.json b/libs/typescript/computer/package.json index cd917772..9c36d415 100644 --- a/libs/typescript/computer/package.json +++ b/libs/typescript/computer/package.json @@ -38,6 +38,7 @@ "prepublishOnly": "pnpm run build" }, "dependencies": { + "@cua/core": "link:../core", "pino": "^9.7.0", "ws": "^8.18.0" }, diff --git a/libs/typescript/computer/pnpm-lock.yaml b/libs/typescript/computer/pnpm-lock.yaml index e1b1e8f5..10c0a8b1 100644 --- a/libs/typescript/computer/pnpm-lock.yaml +++ b/libs/typescript/computer/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@cua/core': + specifier: link:../core + version: link:../core pino: specifier: ^9.7.0 version: 9.7.0 diff --git a/libs/typescript/computer/pnpm-workspace.yaml b/libs/typescript/computer/pnpm-workspace.yaml deleted file mode 100644 index e9796de5..00000000 --- a/libs/typescript/computer/pnpm-workspace.yaml +++ /dev/null @@ -1,3 +0,0 @@ -onlyBuiltDependencies: - - "@biomejs/biome" - - sharp diff --git a/libs/typescript/computer/src/computer/providers/base.ts b/libs/typescript/computer/src/computer/providers/base.ts index 78cdcc8b..fc91b773 100644 --- a/libs/typescript/computer/src/computer/providers/base.ts +++ b/libs/typescript/computer/src/computer/providers/base.ts @@ -1,3 +1,4 @@ +import { Telemetry } from "@cua/core"; import type { OSType } from "../../types"; import type { BaseComputerConfig, Display, VMProviderType } from "../types"; import pino from "pino"; @@ -11,10 +12,18 @@ export abstract class BaseComputer { protected name: string; protected osType: OSType; protected vmProvider?: VMProviderType; + protected telemetry: Telemetry constructor(config: BaseComputerConfig) { this.name = config.name; this.osType = config.osType; + this.telemetry = new Telemetry(); + this.telemetry.recordEvent('module_init', { + module: "computer", + version: process.env.npm_package_version, + node_version: process.version, + }, +) } /** diff --git a/libs/typescript/core/biome.json b/libs/typescript/core/biome.json deleted file mode 100644 index 3251a52d..00000000 --- a/libs/typescript/core/biome.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", - "vcs": { - "enabled": false, - "clientKind": "git", - "useIgnoreFile": false - }, - "files": { - "ignoreUnknown": false, - "ignore": [ - "dist", - "node_modules" - ] - }, - "formatter": { - "enabled": true, - "useEditorconfig": true, - "formatWithErrors": false, - "indentStyle": "space", - "indentWidth": 2, - "lineEnding": "lf", - "lineWidth": 80, - "attributePosition": "auto", - "bracketSpacing": true - }, - "organizeImports": { - "enabled": true - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "style": { - "useSelfClosingElements": "warn", - "noUnusedTemplateLiteral": "warn", - "noNonNullAssertion": "off" - }, - "a11y": { - "useMediaCaption": "off", - "useKeyWithClickEvents": "warn", - "useKeyWithMouseEvents": "warn", - "noSvgWithoutTitle": "off", - "useButtonType": "warn", - "noAutofocus": "off" - }, - "suspicious": { - "noArrayIndexKey": "off" - }, - "correctness": { - "noUnusedVariables": "warn", - "noUnusedFunctionParameters": "warn", - "noUnusedImports": "warn" - }, - "complexity": { - "useOptionalChain": "info" - }, - "nursery": { - "useSortedClasses": { - "level": "warn", - "fix": "safe", - "options": { - "attributes": [ - "className" - ], - "functions": [ - "cn" - ] - } - } - } - } - }, - "javascript": { - "formatter": { - "jsxQuoteStyle": "double", - "quoteProperties": "asNeeded", - "trailingCommas": "es5", - "semicolons": "always", - "arrowParentheses": "always", - "bracketSameLine": false, - "quoteStyle": "single", - "attributePosition": "auto", - "bracketSpacing": true - } - } -} \ No newline at end of file diff --git a/libs/typescript/core/package.json b/libs/typescript/core/package.json index f9b0109c..36f18237 100644 --- a/libs/typescript/core/package.json +++ b/libs/typescript/core/package.json @@ -38,7 +38,10 @@ "prepublishOnly": "pnpm run build" }, "dependencies": { - "pino": "^9.7.0" + "@types/uuid": "^10.0.0", + "pino": "^9.7.0", + "posthog-node": "^5.1.1", + "uuid": "^11.1.0" }, "devDependencies": { "@biomejs/biome": "^1.9.4", diff --git a/libs/typescript/core/pnpm-lock.yaml b/libs/typescript/core/pnpm-lock.yaml index 2f35a784..3f72d007 100644 --- a/libs/typescript/core/pnpm-lock.yaml +++ b/libs/typescript/core/pnpm-lock.yaml @@ -1,33 +1,41 @@ -lockfileVersion: "9.0" +lockfileVersion: '9.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false importers: + .: + dependencies: + '@types/uuid': + specifier: ^10.0.0 + version: 10.0.0 + pino: + specifier: ^9.7.0 + version: 9.7.0 + posthog-node: + specifier: ^5.1.1 + version: 5.1.1 + uuid: + specifier: ^11.1.0 + version: 11.1.0 devDependencies: - "@sxzz/eslint-config": - specifier: ^7.0.1 - version: 7.0.4(@typescript-eslint/eslint-plugin@8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(@typescript-eslint/utils@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) - "@sxzz/prettier-config": - specifier: ^2.2.1 - version: 2.2.1 - "@types/node": + '@biomejs/biome': + specifier: ^1.9.4 + version: 1.9.4 + '@types/node': specifier: ^22.15.17 version: 22.15.32 + '@types/ws': + specifier: ^8.18.1 + version: 8.18.1 bumpp: specifier: ^10.1.0 version: 10.2.0 - eslint: - specifier: ^9.26.0 - version: 9.29.0(jiti@2.4.2) happy-dom: specifier: ^17.4.7 version: 17.6.3 - prettier: - specifier: ^3.5.3 - version: 3.6.0 tsdown: specifier: ^0.11.9 version: 0.11.13(typescript@5.8.3) @@ -42,1135 +50,463 @@ importers: version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.32)(happy-dom@17.6.3)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) packages: - "@babel/generator@7.27.5": - resolution: - { - integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==, - } - engines: { node: ">=6.9.0" } - "@babel/helper-string-parser@7.27.1": - resolution: - { - integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==, - } - engines: { node: ">=6.9.0" } + '@babel/generator@7.27.5': + resolution: {integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==} + engines: {node: '>=6.9.0'} - "@babel/helper-validator-identifier@7.27.1": - resolution: - { - integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==, - } - engines: { node: ">=6.9.0" } + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} - "@babel/parser@7.27.5": - resolution: - { - integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==, - } - engines: { node: ">=6.0.0" } + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.27.5': + resolution: {integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==} + engines: {node: '>=6.0.0'} hasBin: true - "@babel/types@7.27.6": - resolution: - { - integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==, - } - engines: { node: ">=6.9.0" } + '@babel/types@7.27.6': + resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==} + engines: {node: '>=6.9.0'} - "@emnapi/core@1.4.3": - resolution: - { - integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==, - } + '@biomejs/biome@1.9.4': + resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} + engines: {node: '>=14.21.3'} + hasBin: true - "@emnapi/runtime@1.4.3": - resolution: - { - integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==, - } + '@biomejs/cli-darwin-arm64@1.9.4': + resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] - "@emnapi/wasi-threads@1.0.2": - resolution: - { - integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==, - } + '@biomejs/cli-darwin-x64@1.9.4': + resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] - "@es-joy/jsdoccomment@0.50.2": - resolution: - { - integrity: sha512-YAdE/IJSpwbOTiaURNCKECdAwqrJuFiZhylmesBcIRawtYKnBR2wxPhoIewMg+Yu+QuYvHfJNReWpoxGBKOChA==, - } - engines: { node: ">=18" } + '@biomejs/cli-linux-arm64-musl@1.9.4': + resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] - "@es-joy/jsdoccomment@0.52.0": - resolution: - { - integrity: sha512-BXuN7BII+8AyNtn57euU2Yxo9yA/KUDNzrpXyi3pfqKmBhhysR6ZWOebFh3vyPoqA3/j1SOvGgucElMGwlXing==, - } - engines: { node: ">=20.11.0" } + '@biomejs/cli-linux-arm64@1.9.4': + resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] - "@esbuild/aix-ppc64@0.25.5": - resolution: - { - integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==, - } - engines: { node: ">=18" } + '@biomejs/cli-linux-x64-musl@1.9.4': + resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@1.9.4': + resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@1.9.4': + resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@1.9.4': + resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + + '@emnapi/core@1.4.3': + resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} + + '@emnapi/runtime@1.4.3': + resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} + + '@emnapi/wasi-threads@1.0.2': + resolution: {integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==} + + '@esbuild/aix-ppc64@0.25.5': + resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} + engines: {node: '>=18'} cpu: [ppc64] os: [aix] - "@esbuild/android-arm64@0.25.5": - resolution: - { - integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==, - } - engines: { node: ">=18" } + '@esbuild/android-arm64@0.25.5': + resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} + engines: {node: '>=18'} cpu: [arm64] os: [android] - "@esbuild/android-arm@0.25.5": - resolution: - { - integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==, - } - engines: { node: ">=18" } + '@esbuild/android-arm@0.25.5': + resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} + engines: {node: '>=18'} cpu: [arm] os: [android] - "@esbuild/android-x64@0.25.5": - resolution: - { - integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==, - } - engines: { node: ">=18" } + '@esbuild/android-x64@0.25.5': + resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} + engines: {node: '>=18'} cpu: [x64] os: [android] - "@esbuild/darwin-arm64@0.25.5": - resolution: - { - integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==, - } - engines: { node: ">=18" } + '@esbuild/darwin-arm64@0.25.5': + resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} + engines: {node: '>=18'} cpu: [arm64] os: [darwin] - "@esbuild/darwin-x64@0.25.5": - resolution: - { - integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==, - } - engines: { node: ">=18" } + '@esbuild/darwin-x64@0.25.5': + resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} + engines: {node: '>=18'} cpu: [x64] os: [darwin] - "@esbuild/freebsd-arm64@0.25.5": - resolution: - { - integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==, - } - engines: { node: ">=18" } + '@esbuild/freebsd-arm64@0.25.5': + resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} + engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - "@esbuild/freebsd-x64@0.25.5": - resolution: - { - integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==, - } - engines: { node: ">=18" } + '@esbuild/freebsd-x64@0.25.5': + resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} + engines: {node: '>=18'} cpu: [x64] os: [freebsd] - "@esbuild/linux-arm64@0.25.5": - resolution: - { - integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==, - } - engines: { node: ">=18" } + '@esbuild/linux-arm64@0.25.5': + resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} + engines: {node: '>=18'} cpu: [arm64] os: [linux] - "@esbuild/linux-arm@0.25.5": - resolution: - { - integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==, - } - engines: { node: ">=18" } + '@esbuild/linux-arm@0.25.5': + resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} + engines: {node: '>=18'} cpu: [arm] os: [linux] - "@esbuild/linux-ia32@0.25.5": - resolution: - { - integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==, - } - engines: { node: ">=18" } + '@esbuild/linux-ia32@0.25.5': + resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} + engines: {node: '>=18'} cpu: [ia32] os: [linux] - "@esbuild/linux-loong64@0.25.5": - resolution: - { - integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==, - } - engines: { node: ">=18" } + '@esbuild/linux-loong64@0.25.5': + resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} + engines: {node: '>=18'} cpu: [loong64] os: [linux] - "@esbuild/linux-mips64el@0.25.5": - resolution: - { - integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==, - } - engines: { node: ">=18" } + '@esbuild/linux-mips64el@0.25.5': + resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} + engines: {node: '>=18'} cpu: [mips64el] os: [linux] - "@esbuild/linux-ppc64@0.25.5": - resolution: - { - integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==, - } - engines: { node: ">=18" } + '@esbuild/linux-ppc64@0.25.5': + resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} + engines: {node: '>=18'} cpu: [ppc64] os: [linux] - "@esbuild/linux-riscv64@0.25.5": - resolution: - { - integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==, - } - engines: { node: ">=18" } + '@esbuild/linux-riscv64@0.25.5': + resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} + engines: {node: '>=18'} cpu: [riscv64] os: [linux] - "@esbuild/linux-s390x@0.25.5": - resolution: - { - integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==, - } - engines: { node: ">=18" } + '@esbuild/linux-s390x@0.25.5': + resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} + engines: {node: '>=18'} cpu: [s390x] os: [linux] - "@esbuild/linux-x64@0.25.5": - resolution: - { - integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==, - } - engines: { node: ">=18" } + '@esbuild/linux-x64@0.25.5': + resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} + engines: {node: '>=18'} cpu: [x64] os: [linux] - "@esbuild/netbsd-arm64@0.25.5": - resolution: - { - integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==, - } - engines: { node: ">=18" } + '@esbuild/netbsd-arm64@0.25.5': + resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} + engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - "@esbuild/netbsd-x64@0.25.5": - resolution: - { - integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==, - } - engines: { node: ">=18" } + '@esbuild/netbsd-x64@0.25.5': + resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} + engines: {node: '>=18'} cpu: [x64] os: [netbsd] - "@esbuild/openbsd-arm64@0.25.5": - resolution: - { - integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==, - } - engines: { node: ">=18" } + '@esbuild/openbsd-arm64@0.25.5': + resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} + engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - "@esbuild/openbsd-x64@0.25.5": - resolution: - { - integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==, - } - engines: { node: ">=18" } + '@esbuild/openbsd-x64@0.25.5': + resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} + engines: {node: '>=18'} cpu: [x64] os: [openbsd] - "@esbuild/sunos-x64@0.25.5": - resolution: - { - integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==, - } - engines: { node: ">=18" } + '@esbuild/sunos-x64@0.25.5': + resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} + engines: {node: '>=18'} cpu: [x64] os: [sunos] - "@esbuild/win32-arm64@0.25.5": - resolution: - { - integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==, - } - engines: { node: ">=18" } + '@esbuild/win32-arm64@0.25.5': + resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} + engines: {node: '>=18'} cpu: [arm64] os: [win32] - "@esbuild/win32-ia32@0.25.5": - resolution: - { - integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==, - } - engines: { node: ">=18" } + '@esbuild/win32-ia32@0.25.5': + resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} + engines: {node: '>=18'} cpu: [ia32] os: [win32] - "@esbuild/win32-x64@0.25.5": - resolution: - { - integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==, - } - engines: { node: ">=18" } + '@esbuild/win32-x64@0.25.5': + resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} + engines: {node: '>=18'} cpu: [x64] os: [win32] - "@eslint-community/eslint-plugin-eslint-comments@4.5.0": - resolution: - { - integrity: sha512-MAhuTKlr4y/CE3WYX26raZjy+I/kS2PLKSzvfmDCGrBLTFHOYwqROZdr4XwPgXwX3K9rjzMr4pSmUWGnzsUyMg==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} - "@eslint-community/eslint-utils@4.7.0": - resolution: - { - integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} - "@eslint-community/regexpp@4.12.1": - resolution: - { - integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==, - } - engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} - "@eslint/compat@1.3.0": - resolution: - { - integrity: sha512-ZBygRBqpDYiIHsN+d1WyHn3TYgzgpzLEcgJUxTATyiInQbKZz6wZb6+ljwdg8xeeOe4v03z6Uh6lELiw0/mVhQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - peerDependencies: - eslint: ^9.10.0 - peerDependenciesMeta: - eslint: - optional: true + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - "@eslint/config-array@0.20.1": - resolution: - { - integrity: sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - "@eslint/config-helpers@0.2.3": - resolution: - { - integrity: sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@napi-rs/wasm-runtime@0.2.11': + resolution: {integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==} - "@eslint/core@0.13.0": - resolution: - { - integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@oxc-project/types@0.70.0': + resolution: {integrity: sha512-ngyLUpUjO3dpqygSRQDx7nMx8+BmXbWOU4oIwTJFV2MVIDG7knIZwgdwXlQWLg3C3oxg1lS7ppMtPKqKFb7wzw==} - "@eslint/core@0.14.0": - resolution: - { - integrity: sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } + '@quansync/fs@0.1.3': + resolution: {integrity: sha512-G0OnZbMWEs5LhDyqy2UL17vGhSVHkQIfVojMtEWVenvj0V5S84VBgy86kJIuNsGDp2p7sTKlpSIpBUWdC35OKg==} + engines: {node: '>=20.0.0'} - "@eslint/core@0.15.0": - resolution: - { - integrity: sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/eslintrc@3.3.1": - resolution: - { - integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/js@9.29.0": - resolution: - { - integrity: sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/markdown@6.6.0": - resolution: - { - integrity: sha512-IsWPy2jU3gaQDlioDC4sT4I4kG1hX1OMWs/q2sWwJrPoMASHW/Z4SDw+6Aql6EsHejGbagYuJbFq9Zvx+Y1b1Q==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/object-schema@2.1.6": - resolution: - { - integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/plugin-kit@0.2.8": - resolution: - { - integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@eslint/plugin-kit@0.3.2": - resolution: - { - integrity: sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@humanfs/core@0.19.1": - resolution: - { - integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==, - } - engines: { node: ">=18.18.0" } - - "@humanfs/node@0.16.6": - resolution: - { - integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==, - } - engines: { node: ">=18.18.0" } - - "@humanwhocodes/module-importer@1.0.1": - resolution: - { - integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==, - } - engines: { node: ">=12.22" } - - "@humanwhocodes/retry@0.3.1": - resolution: - { - integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==, - } - engines: { node: ">=18.18" } - - "@humanwhocodes/retry@0.4.3": - resolution: - { - integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==, - } - engines: { node: ">=18.18" } - - "@isaacs/balanced-match@4.0.1": - resolution: - { - integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==, - } - engines: { node: 20 || >=22 } - - "@isaacs/brace-expansion@5.0.0": - resolution: - { - integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==, - } - engines: { node: 20 || >=22 } - - "@jridgewell/gen-mapping@0.3.8": - resolution: - { - integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==, - } - engines: { node: ">=6.0.0" } - - "@jridgewell/resolve-uri@3.1.2": - resolution: - { - integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==, - } - engines: { node: ">=6.0.0" } - - "@jridgewell/set-array@1.2.1": - resolution: - { - integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==, - } - engines: { node: ">=6.0.0" } - - "@jridgewell/sourcemap-codec@1.5.0": - resolution: - { - integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==, - } - - "@jridgewell/trace-mapping@0.3.25": - resolution: - { - integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==, - } - - "@napi-rs/wasm-runtime@0.2.11": - resolution: - { - integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==, - } - - "@nodelib/fs.scandir@2.1.5": - resolution: - { - integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==, - } - engines: { node: ">= 8" } - - "@nodelib/fs.stat@2.0.5": - resolution: - { - integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==, - } - engines: { node: ">= 8" } - - "@nodelib/fs.walk@1.2.8": - resolution: - { - integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==, - } - engines: { node: ">= 8" } - - "@oxc-project/types@0.70.0": - resolution: - { - integrity: sha512-ngyLUpUjO3dpqygSRQDx7nMx8+BmXbWOU4oIwTJFV2MVIDG7knIZwgdwXlQWLg3C3oxg1lS7ppMtPKqKFb7wzw==, - } - - "@pkgr/core@0.2.7": - resolution: - { - integrity: sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==, - } - engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } - - "@quansync/fs@0.1.3": - resolution: - { - integrity: sha512-G0OnZbMWEs5LhDyqy2UL17vGhSVHkQIfVojMtEWVenvj0V5S84VBgy86kJIuNsGDp2p7sTKlpSIpBUWdC35OKg==, - } - engines: { node: ">=20.0.0" } - - "@rolldown/binding-darwin-arm64@1.0.0-beta.9": - resolution: - { - integrity: sha512-geUG/FUpm+membLC0NQBb39vVyOfguYZ2oyXc7emr6UjH6TeEECT4b0CPZXKFnELareTiU/Jfl70/eEgNxyQeA==, - } + '@rolldown/binding-darwin-arm64@1.0.0-beta.9': + resolution: {integrity: sha512-geUG/FUpm+membLC0NQBb39vVyOfguYZ2oyXc7emr6UjH6TeEECT4b0CPZXKFnELareTiU/Jfl70/eEgNxyQeA==} cpu: [arm64] os: [darwin] - "@rolldown/binding-darwin-x64@1.0.0-beta.9": - resolution: - { - integrity: sha512-7wPXDwcOtv2I+pWTL2UNpNAxMAGukgBT90Jz4DCfwaYdGvQncF7J0S7IWrRVsRFhBavxM+65RcueE3VXw5UIbg==, - } + '@rolldown/binding-darwin-x64@1.0.0-beta.9': + resolution: {integrity: sha512-7wPXDwcOtv2I+pWTL2UNpNAxMAGukgBT90Jz4DCfwaYdGvQncF7J0S7IWrRVsRFhBavxM+65RcueE3VXw5UIbg==} cpu: [x64] os: [darwin] - "@rolldown/binding-freebsd-x64@1.0.0-beta.9": - resolution: - { - integrity: sha512-agO5mONTNKVrcIt4SRxw5Ni0FOVV3gaH8dIiNp1A4JeU91b9kw7x+JRuNJAQuM2X3pYqVvA6qh13UTNOsaqM/Q==, - } + '@rolldown/binding-freebsd-x64@1.0.0-beta.9': + resolution: {integrity: sha512-agO5mONTNKVrcIt4SRxw5Ni0FOVV3gaH8dIiNp1A4JeU91b9kw7x+JRuNJAQuM2X3pYqVvA6qh13UTNOsaqM/Q==} cpu: [x64] os: [freebsd] - "@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9": - resolution: - { - integrity: sha512-dDNDV9p/8WYDriS9HCcbH6y6+JP38o3enj/pMkdkmkxEnZ0ZoHIfQ9RGYWeRYU56NKBCrya4qZBJx49Jk9LRug==, - } + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9': + resolution: {integrity: sha512-dDNDV9p/8WYDriS9HCcbH6y6+JP38o3enj/pMkdkmkxEnZ0ZoHIfQ9RGYWeRYU56NKBCrya4qZBJx49Jk9LRug==} cpu: [arm] os: [linux] - "@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9": - resolution: - { - integrity: sha512-kZKegmHG1ZvfsFIwYU6DeFSxSIcIliXzeznsJHUo9D9/dlVSDi/PUvsRKcuJkQjZoejM6pk8MHN/UfgGdIhPHw==, - } + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9': + resolution: {integrity: sha512-kZKegmHG1ZvfsFIwYU6DeFSxSIcIliXzeznsJHUo9D9/dlVSDi/PUvsRKcuJkQjZoejM6pk8MHN/UfgGdIhPHw==} cpu: [arm64] os: [linux] - "@rolldown/binding-linux-arm64-musl@1.0.0-beta.9": - resolution: - { - integrity: sha512-f+VL8mO31pyMJiJPr2aA1ryYONkP2UqgbwK7fKtKHZIeDd/AoUGn3+ujPqDhuy2NxgcJ5H8NaSvDpG1tJMHh+g==, - } + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.9': + resolution: {integrity: sha512-f+VL8mO31pyMJiJPr2aA1ryYONkP2UqgbwK7fKtKHZIeDd/AoUGn3+ujPqDhuy2NxgcJ5H8NaSvDpG1tJMHh+g==} cpu: [arm64] os: [linux] - "@rolldown/binding-linux-x64-gnu@1.0.0-beta.9": - resolution: - { - integrity: sha512-GiUEZ0WPjX5LouDoC3O8aJa4h6BLCpIvaAboNw5JoRour/3dC6rbtZZ/B5FC3/ySsN3/dFOhAH97ylQxoZJi7A==, - } + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.9': + resolution: {integrity: sha512-GiUEZ0WPjX5LouDoC3O8aJa4h6BLCpIvaAboNw5JoRour/3dC6rbtZZ/B5FC3/ySsN3/dFOhAH97ylQxoZJi7A==} cpu: [x64] os: [linux] - "@rolldown/binding-linux-x64-musl@1.0.0-beta.9": - resolution: - { - integrity: sha512-AMb0dicw+QHh6RxvWo4BRcuTMgS0cwUejJRMpSyIcHYnKTbj6nUW4HbWNQuDfZiF27l6F5gEwBS+YLUdVzL9vg==, - } + '@rolldown/binding-linux-x64-musl@1.0.0-beta.9': + resolution: {integrity: sha512-AMb0dicw+QHh6RxvWo4BRcuTMgS0cwUejJRMpSyIcHYnKTbj6nUW4HbWNQuDfZiF27l6F5gEwBS+YLUdVzL9vg==} cpu: [x64] os: [linux] - "@rolldown/binding-wasm32-wasi@1.0.0-beta.9": - resolution: - { - integrity: sha512-+pdaiTx7L8bWKvsAuCE0HAxP1ze1WOLoWGCawcrZbMSY10dMh2i82lJiH6tXGXbfYYwsNWhWE2NyG4peFZvRfQ==, - } - engines: { node: ">=14.21.3" } + '@rolldown/binding-wasm32-wasi@1.0.0-beta.9': + resolution: {integrity: sha512-+pdaiTx7L8bWKvsAuCE0HAxP1ze1WOLoWGCawcrZbMSY10dMh2i82lJiH6tXGXbfYYwsNWhWE2NyG4peFZvRfQ==} + engines: {node: '>=14.21.3'} cpu: [wasm32] - "@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9": - resolution: - { - integrity: sha512-A7kN248viWvb8eZMzQu024TBKGoyoVYBsDG2DtoP8u2pzwoh5yDqUL291u01o4f8uzpUHq8mfwQJmcGChFu8KQ==, - } + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9': + resolution: {integrity: sha512-A7kN248viWvb8eZMzQu024TBKGoyoVYBsDG2DtoP8u2pzwoh5yDqUL291u01o4f8uzpUHq8mfwQJmcGChFu8KQ==} cpu: [arm64] os: [win32] - "@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9": - resolution: - { - integrity: sha512-DzKN7iEYjAP8AK8F2G2aCej3fk43Y/EQrVrR3gF0XREes56chjQ7bXIhw819jv74BbxGdnpPcslhet/cgt7WRA==, - } + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9': + resolution: {integrity: sha512-DzKN7iEYjAP8AK8F2G2aCej3fk43Y/EQrVrR3gF0XREes56chjQ7bXIhw819jv74BbxGdnpPcslhet/cgt7WRA==} cpu: [ia32] os: [win32] - "@rolldown/binding-win32-x64-msvc@1.0.0-beta.9": - resolution: - { - integrity: sha512-GMWgTvvbZ8TfBsAiJpoz4SRq3IN3aUMn0rYm8q4I8dcEk4J1uISyfb6ZMzvqW+cvScTWVKWZNqnrmYOKLLUt4w==, - } + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.9': + resolution: {integrity: sha512-GMWgTvvbZ8TfBsAiJpoz4SRq3IN3aUMn0rYm8q4I8dcEk4J1uISyfb6ZMzvqW+cvScTWVKWZNqnrmYOKLLUt4w==} cpu: [x64] os: [win32] - "@rolldown/pluginutils@1.0.0-beta.9": - resolution: - { - integrity: sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==, - } + '@rolldown/pluginutils@1.0.0-beta.9': + resolution: {integrity: sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==} - "@rollup/rollup-android-arm-eabi@4.44.0": - resolution: - { - integrity: sha512-xEiEE5oDW6tK4jXCAyliuntGR+amEMO7HLtdSshVuhFnKTYoeYMyXQK7pLouAJJj5KHdwdn87bfHAR2nSdNAUA==, - } + '@rollup/rollup-android-arm-eabi@4.44.0': + resolution: {integrity: sha512-xEiEE5oDW6tK4jXCAyliuntGR+amEMO7HLtdSshVuhFnKTYoeYMyXQK7pLouAJJj5KHdwdn87bfHAR2nSdNAUA==} cpu: [arm] os: [android] - "@rollup/rollup-android-arm64@4.44.0": - resolution: - { - integrity: sha512-uNSk/TgvMbskcHxXYHzqwiyBlJ/lGcv8DaUfcnNwict8ba9GTTNxfn3/FAoFZYgkaXXAdrAA+SLyKplyi349Jw==, - } + '@rollup/rollup-android-arm64@4.44.0': + resolution: {integrity: sha512-uNSk/TgvMbskcHxXYHzqwiyBlJ/lGcv8DaUfcnNwict8ba9GTTNxfn3/FAoFZYgkaXXAdrAA+SLyKplyi349Jw==} cpu: [arm64] os: [android] - "@rollup/rollup-darwin-arm64@4.44.0": - resolution: - { - integrity: sha512-VGF3wy0Eq1gcEIkSCr8Ke03CWT+Pm2yveKLaDvq51pPpZza3JX/ClxXOCmTYYq3us5MvEuNRTaeyFThCKRQhOA==, - } + '@rollup/rollup-darwin-arm64@4.44.0': + resolution: {integrity: sha512-VGF3wy0Eq1gcEIkSCr8Ke03CWT+Pm2yveKLaDvq51pPpZza3JX/ClxXOCmTYYq3us5MvEuNRTaeyFThCKRQhOA==} cpu: [arm64] os: [darwin] - "@rollup/rollup-darwin-x64@4.44.0": - resolution: - { - integrity: sha512-fBkyrDhwquRvrTxSGH/qqt3/T0w5Rg0L7ZIDypvBPc1/gzjJle6acCpZ36blwuwcKD/u6oCE/sRWlUAcxLWQbQ==, - } + '@rollup/rollup-darwin-x64@4.44.0': + resolution: {integrity: sha512-fBkyrDhwquRvrTxSGH/qqt3/T0w5Rg0L7ZIDypvBPc1/gzjJle6acCpZ36blwuwcKD/u6oCE/sRWlUAcxLWQbQ==} cpu: [x64] os: [darwin] - "@rollup/rollup-freebsd-arm64@4.44.0": - resolution: - { - integrity: sha512-u5AZzdQJYJXByB8giQ+r4VyfZP+walV+xHWdaFx/1VxsOn6eWJhK2Vl2eElvDJFKQBo/hcYIBg/jaKS8ZmKeNQ==, - } + '@rollup/rollup-freebsd-arm64@4.44.0': + resolution: {integrity: sha512-u5AZzdQJYJXByB8giQ+r4VyfZP+walV+xHWdaFx/1VxsOn6eWJhK2Vl2eElvDJFKQBo/hcYIBg/jaKS8ZmKeNQ==} cpu: [arm64] os: [freebsd] - "@rollup/rollup-freebsd-x64@4.44.0": - resolution: - { - integrity: sha512-qC0kS48c/s3EtdArkimctY7h3nHicQeEUdjJzYVJYR3ct3kWSafmn6jkNCA8InbUdge6PVx6keqjk5lVGJf99g==, - } + '@rollup/rollup-freebsd-x64@4.44.0': + resolution: {integrity: sha512-qC0kS48c/s3EtdArkimctY7h3nHicQeEUdjJzYVJYR3ct3kWSafmn6jkNCA8InbUdge6PVx6keqjk5lVGJf99g==} cpu: [x64] os: [freebsd] - "@rollup/rollup-linux-arm-gnueabihf@4.44.0": - resolution: - { - integrity: sha512-x+e/Z9H0RAWckn4V2OZZl6EmV0L2diuX3QB0uM1r6BvhUIv6xBPL5mrAX2E3e8N8rEHVPwFfz/ETUbV4oW9+lQ==, - } + '@rollup/rollup-linux-arm-gnueabihf@4.44.0': + resolution: {integrity: sha512-x+e/Z9H0RAWckn4V2OZZl6EmV0L2diuX3QB0uM1r6BvhUIv6xBPL5mrAX2E3e8N8rEHVPwFfz/ETUbV4oW9+lQ==} cpu: [arm] os: [linux] - "@rollup/rollup-linux-arm-musleabihf@4.44.0": - resolution: - { - integrity: sha512-1exwiBFf4PU/8HvI8s80icyCcnAIB86MCBdst51fwFmH5dyeoWVPVgmQPcKrMtBQ0W5pAs7jBCWuRXgEpRzSCg==, - } + '@rollup/rollup-linux-arm-musleabihf@4.44.0': + resolution: {integrity: sha512-1exwiBFf4PU/8HvI8s80icyCcnAIB86MCBdst51fwFmH5dyeoWVPVgmQPcKrMtBQ0W5pAs7jBCWuRXgEpRzSCg==} cpu: [arm] os: [linux] - "@rollup/rollup-linux-arm64-gnu@4.44.0": - resolution: - { - integrity: sha512-ZTR2mxBHb4tK4wGf9b8SYg0Y6KQPjGpR4UWwTFdnmjB4qRtoATZ5dWn3KsDwGa5Z2ZBOE7K52L36J9LueKBdOQ==, - } + '@rollup/rollup-linux-arm64-gnu@4.44.0': + resolution: {integrity: sha512-ZTR2mxBHb4tK4wGf9b8SYg0Y6KQPjGpR4UWwTFdnmjB4qRtoATZ5dWn3KsDwGa5Z2ZBOE7K52L36J9LueKBdOQ==} cpu: [arm64] os: [linux] - "@rollup/rollup-linux-arm64-musl@4.44.0": - resolution: - { - integrity: sha512-GFWfAhVhWGd4r6UxmnKRTBwP1qmModHtd5gkraeW2G490BpFOZkFtem8yuX2NyafIP/mGpRJgTJ2PwohQkUY/Q==, - } + '@rollup/rollup-linux-arm64-musl@4.44.0': + resolution: {integrity: sha512-GFWfAhVhWGd4r6UxmnKRTBwP1qmModHtd5gkraeW2G490BpFOZkFtem8yuX2NyafIP/mGpRJgTJ2PwohQkUY/Q==} cpu: [arm64] os: [linux] - "@rollup/rollup-linux-loongarch64-gnu@4.44.0": - resolution: - { - integrity: sha512-xw+FTGcov/ejdusVOqKgMGW3c4+AgqrfvzWEVXcNP6zq2ue+lsYUgJ+5Rtn/OTJf7e2CbgTFvzLW2j0YAtj0Gg==, - } + '@rollup/rollup-linux-loongarch64-gnu@4.44.0': + resolution: {integrity: sha512-xw+FTGcov/ejdusVOqKgMGW3c4+AgqrfvzWEVXcNP6zq2ue+lsYUgJ+5Rtn/OTJf7e2CbgTFvzLW2j0YAtj0Gg==} cpu: [loong64] os: [linux] - "@rollup/rollup-linux-powerpc64le-gnu@4.44.0": - resolution: - { - integrity: sha512-bKGibTr9IdF0zr21kMvkZT4K6NV+jjRnBoVMt2uNMG0BYWm3qOVmYnXKzx7UhwrviKnmK46IKMByMgvpdQlyJQ==, - } + '@rollup/rollup-linux-powerpc64le-gnu@4.44.0': + resolution: {integrity: sha512-bKGibTr9IdF0zr21kMvkZT4K6NV+jjRnBoVMt2uNMG0BYWm3qOVmYnXKzx7UhwrviKnmK46IKMByMgvpdQlyJQ==} cpu: [ppc64] os: [linux] - "@rollup/rollup-linux-riscv64-gnu@4.44.0": - resolution: - { - integrity: sha512-vV3cL48U5kDaKZtXrti12YRa7TyxgKAIDoYdqSIOMOFBXqFj2XbChHAtXquEn2+n78ciFgr4KIqEbydEGPxXgA==, - } + '@rollup/rollup-linux-riscv64-gnu@4.44.0': + resolution: {integrity: sha512-vV3cL48U5kDaKZtXrti12YRa7TyxgKAIDoYdqSIOMOFBXqFj2XbChHAtXquEn2+n78ciFgr4KIqEbydEGPxXgA==} cpu: [riscv64] os: [linux] - "@rollup/rollup-linux-riscv64-musl@4.44.0": - resolution: - { - integrity: sha512-TDKO8KlHJuvTEdfw5YYFBjhFts2TR0VpZsnLLSYmB7AaohJhM8ctDSdDnUGq77hUh4m/djRafw+9zQpkOanE2Q==, - } + '@rollup/rollup-linux-riscv64-musl@4.44.0': + resolution: {integrity: sha512-TDKO8KlHJuvTEdfw5YYFBjhFts2TR0VpZsnLLSYmB7AaohJhM8ctDSdDnUGq77hUh4m/djRafw+9zQpkOanE2Q==} cpu: [riscv64] os: [linux] - "@rollup/rollup-linux-s390x-gnu@4.44.0": - resolution: - { - integrity: sha512-8541GEyktXaw4lvnGp9m84KENcxInhAt6vPWJ9RodsB/iGjHoMB2Pp5MVBCiKIRxrxzJhGCxmNzdu+oDQ7kwRA==, - } + '@rollup/rollup-linux-s390x-gnu@4.44.0': + resolution: {integrity: sha512-8541GEyktXaw4lvnGp9m84KENcxInhAt6vPWJ9RodsB/iGjHoMB2Pp5MVBCiKIRxrxzJhGCxmNzdu+oDQ7kwRA==} cpu: [s390x] os: [linux] - "@rollup/rollup-linux-x64-gnu@4.44.0": - resolution: - { - integrity: sha512-iUVJc3c0o8l9Sa/qlDL2Z9UP92UZZW1+EmQ4xfjTc1akr0iUFZNfxrXJ/R1T90h/ILm9iXEY6+iPrmYB3pXKjw==, - } + '@rollup/rollup-linux-x64-gnu@4.44.0': + resolution: {integrity: sha512-iUVJc3c0o8l9Sa/qlDL2Z9UP92UZZW1+EmQ4xfjTc1akr0iUFZNfxrXJ/R1T90h/ILm9iXEY6+iPrmYB3pXKjw==} cpu: [x64] os: [linux] - "@rollup/rollup-linux-x64-musl@4.44.0": - resolution: - { - integrity: sha512-PQUobbhLTQT5yz/SPg116VJBgz+XOtXt8D1ck+sfJJhuEsMj2jSej5yTdp8CvWBSceu+WW+ibVL6dm0ptG5fcA==, - } + '@rollup/rollup-linux-x64-musl@4.44.0': + resolution: {integrity: sha512-PQUobbhLTQT5yz/SPg116VJBgz+XOtXt8D1ck+sfJJhuEsMj2jSej5yTdp8CvWBSceu+WW+ibVL6dm0ptG5fcA==} cpu: [x64] os: [linux] - "@rollup/rollup-win32-arm64-msvc@4.44.0": - resolution: - { - integrity: sha512-M0CpcHf8TWn+4oTxJfh7LQuTuaYeXGbk0eageVjQCKzYLsajWS/lFC94qlRqOlyC2KvRT90ZrfXULYmukeIy7w==, - } + '@rollup/rollup-win32-arm64-msvc@4.44.0': + resolution: {integrity: sha512-M0CpcHf8TWn+4oTxJfh7LQuTuaYeXGbk0eageVjQCKzYLsajWS/lFC94qlRqOlyC2KvRT90ZrfXULYmukeIy7w==} cpu: [arm64] os: [win32] - "@rollup/rollup-win32-ia32-msvc@4.44.0": - resolution: - { - integrity: sha512-3XJ0NQtMAXTWFW8FqZKcw3gOQwBtVWP/u8TpHP3CRPXD7Pd6s8lLdH3sHWh8vqKCyyiI8xW5ltJScQmBU9j7WA==, - } + '@rollup/rollup-win32-ia32-msvc@4.44.0': + resolution: {integrity: sha512-3XJ0NQtMAXTWFW8FqZKcw3gOQwBtVWP/u8TpHP3CRPXD7Pd6s8lLdH3sHWh8vqKCyyiI8xW5ltJScQmBU9j7WA==} cpu: [ia32] os: [win32] - "@rollup/rollup-win32-x64-msvc@4.44.0": - resolution: - { - integrity: sha512-Q2Mgwt+D8hd5FIPUuPDsvPR7Bguza6yTkJxspDGkZj7tBRn2y4KSWYuIXpftFSjBra76TbKerCV7rgFPQrn+wQ==, - } + '@rollup/rollup-win32-x64-msvc@4.44.0': + resolution: {integrity: sha512-Q2Mgwt+D8hd5FIPUuPDsvPR7Bguza6yTkJxspDGkZj7tBRn2y4KSWYuIXpftFSjBra76TbKerCV7rgFPQrn+wQ==} cpu: [x64] os: [win32] - "@sxzz/eslint-config@7.0.4": - resolution: - { - integrity: sha512-4o3stQQ5wVdQXJ4zWwHlfC5Mv489Ob6A7bmEnt2GGOr675eVV/IFqDdwu1MnbmcQVY5CVXsyhkBCNHQTjDqAiA==, - } - engines: { node: ">=20.0.0" } - peerDependencies: - "@unocss/eslint-plugin": ">=65.0.0" - eslint: ^9.5.0 - peerDependenciesMeta: - "@unocss/eslint-plugin": - optional: true + '@tybys/wasm-util@0.9.0': + resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} - "@sxzz/prettier-config@2.2.1": - resolution: - { - integrity: sha512-4eKrQdzJpMOFrUD9rFm1IfVkpchPvnPOObJvnX+DQB0KHRtHbU0vBwSpOLHioxLPYFwJGjSl6NC0trrCDkCtsA==, - } + '@types/chai@5.2.2': + resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} - "@tybys/wasm-util@0.9.0": - resolution: - { - integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==, - } + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} - "@types/chai@5.2.2": - resolution: - { - integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==, - } + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - "@types/debug@4.1.12": - resolution: - { - integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==, - } + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - "@types/deep-eql@4.0.2": - resolution: - { - integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==, - } + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - "@types/estree@1.0.8": - resolution: - { - integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==, - } + '@types/node@22.15.32': + resolution: {integrity: sha512-3jigKqgSjsH6gYZv2nEsqdXfZqIFGAV36XYYjf9KGZ3PSG+IhLecqPnI310RvjutyMwifE2hhhNEklOUrvx/wA==} - "@types/json-schema@7.0.15": - resolution: - { - integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==, - } + '@types/uuid@10.0.0': + resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} - "@types/mdast@4.0.4": - resolution: - { - integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==, - } + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - "@types/ms@2.1.0": - resolution: - { - integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==, - } + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - "@types/node@22.15.32": - resolution: - { - integrity: sha512-3jigKqgSjsH6gYZv2nEsqdXfZqIFGAV36XYYjf9KGZ3PSG+IhLecqPnI310RvjutyMwifE2hhhNEklOUrvx/wA==, - } - - "@types/unist@3.0.3": - resolution: - { - integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==, - } - - "@typescript-eslint/eslint-plugin@8.34.1": - resolution: - { - integrity: sha512-STXcN6ebF6li4PxwNeFnqF8/2BNDvBupf2OPx2yWNzr6mKNGF7q49VM00Pz5FaomJyqvbXpY6PhO+T9w139YEQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - peerDependencies: - "@typescript-eslint/parser": ^8.34.1 - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - - "@typescript-eslint/parser@8.34.1": - resolution: - { - integrity: sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - - "@typescript-eslint/project-service@8.34.1": - resolution: - { - integrity: sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - peerDependencies: - typescript: ">=4.8.4 <5.9.0" - - "@typescript-eslint/scope-manager@8.34.1": - resolution: - { - integrity: sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@typescript-eslint/tsconfig-utils@8.34.1": - resolution: - { - integrity: sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - peerDependencies: - typescript: ">=4.8.4 <5.9.0" - - "@typescript-eslint/type-utils@8.34.1": - resolution: - { - integrity: sha512-Tv7tCCr6e5m8hP4+xFugcrwTOucB8lshffJ6zf1mF1TbU67R+ntCc6DzLNKM+s/uzDyv8gLq7tufaAhIBYeV8g==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - - "@typescript-eslint/types@8.34.1": - resolution: - { - integrity: sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@typescript-eslint/typescript-estree@8.34.1": - resolution: - { - integrity: sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - peerDependencies: - typescript: ">=4.8.4 <5.9.0" - - "@typescript-eslint/utils@8.34.1": - resolution: - { - integrity: sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - - "@typescript-eslint/visitor-keys@8.34.1": - resolution: - { - integrity: sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - "@unrs/resolver-binding-android-arm-eabi@1.9.1": - resolution: - { - integrity: sha512-dd7yIp1hfJFX9ZlVLQRrh/Re9WMUHHmF9hrKD1yIvxcyNr2BhQ3xc1upAVhy8NijadnCswAxWQu8MkkSMC1qXQ==, - } - cpu: [arm] - os: [android] - - "@unrs/resolver-binding-android-arm64@1.9.1": - resolution: - { - integrity: sha512-EzUPcMFtDVlo5yrbzMqUsGq3HnLXw+3ZOhSd7CUaDmbTtnrzM+RO2ntw2dm2wjbbc5djWj3yX0wzbbg8pLhx8g==, - } - cpu: [arm64] - os: [android] - - "@unrs/resolver-binding-darwin-arm64@1.9.1": - resolution: - { - integrity: sha512-nB+dna3q4kOleKFcSZJ/wDXIsAd1kpMO9XrVAt8tG3RDWJ6vi+Ic6bpz4cmg5tWNeCfHEY4KuqJCB+pKejPEmQ==, - } - cpu: [arm64] - os: [darwin] - - "@unrs/resolver-binding-darwin-x64@1.9.1": - resolution: - { - integrity: sha512-aKWHCrOGaCGwZcekf3TnczQoBxk5w//W3RZ4EQyhux6rKDwBPgDU9Y2yGigCV1Z+8DWqZgVGQi+hdpnlSy3a1w==, - } - cpu: [x64] - os: [darwin] - - "@unrs/resolver-binding-freebsd-x64@1.9.1": - resolution: - { - integrity: sha512-4dIEMXrXt0UqDVgrsUd1I+NoIzVQWXy/CNhgpfS75rOOMK/4Abn0Mx2M2gWH4Mk9+ds/ASAiCmqoUFynmMY5hA==, - } - cpu: [x64] - os: [freebsd] - - "@unrs/resolver-binding-linux-arm-gnueabihf@1.9.1": - resolution: - { - integrity: sha512-vtvS13IXPs1eE8DuS/soiosqMBeyh50YLRZ+p7EaIKAPPeevRnA9G/wu/KbVt01ZD5qiGjxS+CGIdVC7I6gTOw==, - } - cpu: [arm] - os: [linux] - - "@unrs/resolver-binding-linux-arm-musleabihf@1.9.1": - resolution: - { - integrity: sha512-BfdnN6aZ7NcX8djW8SR6GOJc+K+sFhWRF4vJueVE0vbUu5N1bLnBpxJg1TGlhSyo+ImC4SR0jcNiKN0jdoxt+A==, - } - cpu: [arm] - os: [linux] - - "@unrs/resolver-binding-linux-arm64-gnu@1.9.1": - resolution: - { - integrity: sha512-Jhge7lFtH0QqfRz2PyJjJXWENqywPteITd+nOS0L6AhbZli+UmEyGBd2Sstt1c+l9C+j/YvKTl9wJo9PPmsFNg==, - } - cpu: [arm64] - os: [linux] - - "@unrs/resolver-binding-linux-arm64-musl@1.9.1": - resolution: - { - integrity: sha512-ofdK/ow+ZSbSU0pRoB7uBaiRHeaAOYQFU5Spp87LdcPL/P1RhbCTMSIYVb61XWzsVEmYKjHFtoIE0wxP6AFvrA==, - } - cpu: [arm64] - os: [linux] - - "@unrs/resolver-binding-linux-ppc64-gnu@1.9.1": - resolution: - { - integrity: sha512-eC8SXVn8de67HacqU7PoGdHA+9tGbqfEdD05AEFRAB81ejeQtNi5Fx7lPcxpLH79DW0BnMAHau3hi4RVkHfSCw==, - } - cpu: [ppc64] - os: [linux] - - "@unrs/resolver-binding-linux-riscv64-gnu@1.9.1": - resolution: - { - integrity: sha512-fIkwvAAQ41kfoGWfzeJ33iLGShl0JEDZHrMnwTHMErUcPkaaZRJYjQjsFhMl315NEQ4mmTlC+2nfK/J2IszDOw==, - } - cpu: [riscv64] - os: [linux] - - "@unrs/resolver-binding-linux-riscv64-musl@1.9.1": - resolution: - { - integrity: sha512-RAAszxImSOFLk44aLwnSqpcOdce8sBcxASledSzuFAd8Q5ZhhVck472SisspnzHdc7THCvGXiUeZ2hOC7NUoBQ==, - } - cpu: [riscv64] - os: [linux] - - "@unrs/resolver-binding-linux-s390x-gnu@1.9.1": - resolution: - { - integrity: sha512-QoP9vkY+THuQdZi05bA6s6XwFd6HIz3qlx82v9bTOgxeqin/3C12Ye7f7EOD00RQ36OtOPWnhEMMm84sv7d1XQ==, - } - cpu: [s390x] - os: [linux] - - "@unrs/resolver-binding-linux-x64-gnu@1.9.1": - resolution: - { - integrity: sha512-/p77cGN/h9zbsfCseAP5gY7tK+7+DdM8fkPfr9d1ye1fsF6bmtGbtZN6e/8j4jCZ9NEIBBkT0GhdgixSelTK9g==, - } - cpu: [x64] - os: [linux] - - "@unrs/resolver-binding-linux-x64-musl@1.9.1": - resolution: - { - integrity: sha512-wInTqT3Bu9u50mDStEig1v8uxEL2Ht+K8pir/YhyyrM5ordJtxoqzsL1vR/CQzOJuDunUTrDkMM0apjW/d7/PA==, - } - cpu: [x64] - os: [linux] - - "@unrs/resolver-binding-wasm32-wasi@1.9.1": - resolution: - { - integrity: sha512-eNwqO5kUa+1k7yFIircwwiniKWA0UFHo2Cfm8LYgkh9km7uMad+0x7X7oXbQonJXlqfitBTSjhA0un+DsHIrhw==, - } - engines: { node: ">=14.0.0" } - cpu: [wasm32] - - "@unrs/resolver-binding-win32-arm64-msvc@1.9.1": - resolution: - { - integrity: sha512-Eaz1xMUnoa2mFqh20mPqSdbYl6crnk8HnIXDu6nsla9zpgZJZO8w3c1gvNN/4Eb0RXRq3K9OG6mu8vw14gIqiA==, - } - cpu: [arm64] - os: [win32] - - "@unrs/resolver-binding-win32-ia32-msvc@1.9.1": - resolution: - { - integrity: sha512-H/+d+5BGlnEQif0gnwWmYbYv7HJj563PUKJfn8PlmzF8UmF+8KxdvXdwCsoOqh4HHnENnoLrav9NYBrv76x1wQ==, - } - cpu: [ia32] - os: [win32] - - "@unrs/resolver-binding-win32-x64-msvc@1.9.1": - resolution: - { - integrity: sha512-rS86wI4R6cknYM3is3grCb/laE8XBEbpWAMSIPjYfmYp75KL5dT87jXF2orDa4tQYg5aajP5G8Fgh34dRyR+Rw==, - } - cpu: [x64] - os: [win32] - - "@vitest/expect@3.2.4": - resolution: - { - integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==, - } - - "@vitest/mocker@3.2.4": - resolution: - { - integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==, - } + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} peerDependencies: msw: ^2.4.9 vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 @@ -1180,170 +516,50 @@ packages: vite: optional: true - "@vitest/pretty-format@3.2.4": - resolution: - { - integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==, - } + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - "@vitest/runner@3.2.4": - resolution: - { - integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==, - } + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} - "@vitest/snapshot@3.2.4": - resolution: - { - integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==, - } + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} - "@vitest/spy@3.2.4": - resolution: - { - integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==, - } + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} - "@vitest/utils@3.2.4": - resolution: - { - integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==, - } - - acorn-jsx@5.3.2: - resolution: - { - integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, - } - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - - acorn@8.15.0: - resolution: - { - integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==, - } - engines: { node: ">=0.4.0" } - hasBin: true - - ajv@6.12.6: - resolution: - { - integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==, - } - - ansi-styles@4.3.0: - resolution: - { - integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, - } - engines: { node: ">=8" } + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} ansis@4.1.0: - resolution: - { - integrity: sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==, - } - engines: { node: ">=14" } - - are-docs-informative@0.0.2: - resolution: - { - integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==, - } - engines: { node: ">=14" } - - argparse@2.0.1: - resolution: - { - integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, - } + resolution: {integrity: sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==} + engines: {node: '>=14'} args-tokenizer@0.3.0: - resolution: - { - integrity: sha512-xXAd7G2Mll5W8uo37GETpQ2VrE84M181Z7ugHFGQnJZ50M2mbOv0osSZ9VsSgPfJQ+LVG0prSi0th+ELMsno7Q==, - } + resolution: {integrity: sha512-xXAd7G2Mll5W8uo37GETpQ2VrE84M181Z7ugHFGQnJZ50M2mbOv0osSZ9VsSgPfJQ+LVG0prSi0th+ELMsno7Q==} assertion-error@2.0.1: - resolution: - { - integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} ast-kit@2.1.0: - resolution: - { - integrity: sha512-ROM2LlXbZBZVk97crfw8PGDOBzzsJvN2uJCmwswvPUNyfH14eg90mSN3xNqsri1JS1G9cz0VzeDUhxJkTrr4Ew==, - } - engines: { node: ">=20.18.0" } + resolution: {integrity: sha512-ROM2LlXbZBZVk97crfw8PGDOBzzsJvN2uJCmwswvPUNyfH14eg90mSN3xNqsri1JS1G9cz0VzeDUhxJkTrr4Ew==} + engines: {node: '>=20.18.0'} - balanced-match@1.0.2: - resolution: - { - integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, - } + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} birpc@2.4.0: - resolution: - { - integrity: sha512-5IdNxTyhXHv2UlgnPHQ0h+5ypVmkrYHzL8QT+DwFZ//2N/oNV8Ch+BCRmTJ3x6/z9Axo/cXYBc9eprsUVK/Jsg==, - } - - boolbase@1.0.0: - resolution: - { - integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==, - } - - brace-expansion@1.1.12: - resolution: - { - integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==, - } - - brace-expansion@2.0.2: - resolution: - { - integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==, - } - - braces@3.0.3: - resolution: - { - integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==, - } - engines: { node: ">=8" } - - browserslist@4.25.0: - resolution: - { - integrity: sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==, - } - engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } - hasBin: true - - builtin-modules@5.0.0: - resolution: - { - integrity: sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg==, - } - engines: { node: ">=18.20" } + resolution: {integrity: sha512-5IdNxTyhXHv2UlgnPHQ0h+5ypVmkrYHzL8QT+DwFZ//2N/oNV8Ch+BCRmTJ3x6/z9Axo/cXYBc9eprsUVK/Jsg==} bumpp@10.2.0: - resolution: - { - integrity: sha512-1EJ2NG3M3WYJj4m+GtcxNH6Y7zMQ8q68USMoUGKjM6qFTVXSXCnTxcQSUDV7j4KjLVbk2uK6345Z+6RKOv0w5A==, - } - engines: { node: ">=18" } + resolution: {integrity: sha512-1EJ2NG3M3WYJj4m+GtcxNH6Y7zMQ8q68USMoUGKjM6qFTVXSXCnTxcQSUDV7j4KjLVbk2uK6345Z+6RKOv0w5A==} + engines: {node: '>=18'} hasBin: true c12@3.0.4: - resolution: - { - integrity: sha512-t5FaZTYbbCtvxuZq9xxIruYydrAGsJ+8UdP0pZzMiK2xl/gNiSOy0OxhLzHUEEb0m1QXYqfzfvyIFEmz/g9lqg==, - } + resolution: {integrity: sha512-t5FaZTYbbCtvxuZq9xxIruYydrAGsJ+8UdP0pZzMiK2xl/gNiSOy0OxhLzHUEEb0m1QXYqfzfvyIFEmz/g9lqg==} peerDependencies: magicast: ^0.3.5 peerDependenciesMeta: @@ -1351,1641 +567,242 @@ packages: optional: true cac@6.7.14: - resolution: - { - integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==, - } - engines: { node: ">=8" } - - callsites@3.1.0: - resolution: - { - integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==, - } - engines: { node: ">=6" } - - caniuse-lite@1.0.30001724: - resolution: - { - integrity: sha512-WqJo7p0TbHDOythNTqYujmaJTvtYRZrjpP8TCvH6Vb9CYJerJNKamKzIWOM4BkQatWj9H2lYulpdAQNBe7QhNA==, - } - - ccount@2.0.1: - resolution: - { - integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==, - } + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} chai@5.2.0: - resolution: - { - integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==, - } - engines: { node: ">=12" } - - chalk@4.1.2: - resolution: - { - integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==, - } - engines: { node: ">=10" } - - character-entities@2.0.2: - resolution: - { - integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==, - } + resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} + engines: {node: '>=12'} check-error@2.1.1: - resolution: - { - integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==, - } - engines: { node: ">= 16" } + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} chokidar@4.0.3: - resolution: - { - integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==, - } - engines: { node: ">= 14.16.0" } - - ci-info@4.2.0: - resolution: - { - integrity: sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} citty@0.1.6: - resolution: - { - integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==, - } - - clean-regexp@1.0.0: - resolution: - { - integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==, - } - engines: { node: ">=4" } - - color-convert@2.0.1: - resolution: - { - integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, - } - engines: { node: ">=7.0.0" } - - color-name@1.1.4: - resolution: - { - integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, - } - - comment-parser@1.4.1: - resolution: - { - integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==, - } - engines: { node: ">= 12.0.0" } - - concat-map@0.0.1: - resolution: - { - integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, - } - - confbox@0.1.8: - resolution: - { - integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==, - } + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} confbox@0.2.2: - resolution: - { - integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==, - } + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} consola@3.4.2: - resolution: - { - integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==, - } - engines: { node: ^14.18.0 || >=16.10.0 } - - core-js-compat@3.43.0: - resolution: - { - integrity: sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==, - } - - cross-spawn@7.0.6: - resolution: - { - integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==, - } - engines: { node: ">= 8" } - - cssesc@3.0.0: - resolution: - { - integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==, - } - engines: { node: ">=4" } - hasBin: true + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} debug@4.4.1: - resolution: - { - integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==, - } - engines: { node: ">=6.0" } + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} peerDependencies: - supports-color: "*" + supports-color: '*' peerDependenciesMeta: supports-color: optional: true - decode-named-character-reference@1.2.0: - resolution: - { - integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==, - } - deep-eql@5.0.2: - resolution: - { - integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==, - } - engines: { node: ">=6" } - - deep-is@0.1.4: - resolution: - { - integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, - } + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} defu@6.1.4: - resolution: - { - integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==, - } - - dequal@2.0.3: - resolution: - { - integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} destr@2.0.5: - resolution: - { - integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==, - } - - devlop@1.1.0: - resolution: - { - integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==, - } + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} diff@8.0.2: - resolution: - { - integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==, - } - engines: { node: ">=0.3.1" } + resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} + engines: {node: '>=0.3.1'} dotenv@16.5.0: - resolution: - { - integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} + engines: {node: '>=12'} dts-resolver@2.1.1: - resolution: - { - integrity: sha512-3BiGFhB6mj5Kv+W2vdJseQUYW+SKVzAFJL6YNP6ursbrwy1fXHRotfHi3xLNxe4wZl/K8qbAFeCDjZLjzqxxRw==, - } - engines: { node: ">=20.18.0" } + resolution: {integrity: sha512-3BiGFhB6mj5Kv+W2vdJseQUYW+SKVzAFJL6YNP6ursbrwy1fXHRotfHi3xLNxe4wZl/K8qbAFeCDjZLjzqxxRw==} + engines: {node: '>=20.18.0'} peerDependencies: - oxc-resolver: ">=11.0.0" + oxc-resolver: '>=11.0.0' peerDependenciesMeta: oxc-resolver: optional: true - electron-to-chromium@1.5.171: - resolution: - { - integrity: sha512-scWpzXEJEMrGJa4Y6m/tVotb0WuvNmasv3wWVzUAeCgKU0ToFOhUW6Z+xWnRQANMYGxN4ngJXIThgBJOqzVPCQ==, - } - empathic@1.1.0: - resolution: - { - integrity: sha512-rsPft6CK3eHtrlp9Y5ALBb+hfK+DWnA4WFebbazxjWyx8vSm3rZeoM3z9irsjcqO3PYRzlfv27XIB4tz2DV7RA==, - } - engines: { node: ">=14" } - - enhanced-resolve@5.18.1: - resolution: - { - integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==, - } - engines: { node: ">=10.13.0" } + resolution: {integrity: sha512-rsPft6CK3eHtrlp9Y5ALBb+hfK+DWnA4WFebbazxjWyx8vSm3rZeoM3z9irsjcqO3PYRzlfv27XIB4tz2DV7RA==} + engines: {node: '>=14'} es-module-lexer@1.7.0: - resolution: - { - integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==, - } + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} esbuild@0.25.5: - resolution: - { - integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==, - } - engines: { node: ">=18" } + resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} + engines: {node: '>=18'} hasBin: true escalade@3.2.0: - resolution: - { - integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==, - } - engines: { node: ">=6" } - - escape-string-regexp@1.0.5: - resolution: - { - integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==, - } - engines: { node: ">=0.8.0" } - - escape-string-regexp@4.0.0: - resolution: - { - integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==, - } - engines: { node: ">=10" } - - escape-string-regexp@5.0.0: - resolution: - { - integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==, - } - engines: { node: ">=12" } - - eslint-compat-utils@0.5.1: - resolution: - { - integrity: sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==, - } - engines: { node: ">=12" } - peerDependencies: - eslint: ">=6.0.0" - - eslint-compat-utils@0.6.5: - resolution: - { - integrity: sha512-vAUHYzue4YAa2hNACjB8HvUQj5yehAZgiClyFVVom9cP8z5NSFq3PwB/TtJslN2zAMgRX6FCFCjYBbQh71g5RQ==, - } - engines: { node: ">=12" } - peerDependencies: - eslint: ">=6.0.0" - - eslint-config-flat-gitignore@2.1.0: - resolution: - { - integrity: sha512-cJzNJ7L+psWp5mXM7jBX+fjHtBvvh06RBlcweMhKD8jWqQw0G78hOW5tpVALGHGFPsBV+ot2H+pdDGJy6CV8pA==, - } - peerDependencies: - eslint: ^9.5.0 - - eslint-config-prettier@10.1.5: - resolution: - { - integrity: sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==, - } - hasBin: true - peerDependencies: - eslint: ">=7.0.0" - - eslint-flat-config-utils@2.1.0: - resolution: - { - integrity: sha512-6fjOJ9tS0k28ketkUcQ+kKptB4dBZY2VijMZ9rGn8Cwnn1SH0cZBoPXT8AHBFHxmHcLFQK9zbELDinZ2Mr1rng==, - } - - eslint-import-context@0.1.8: - resolution: - { - integrity: sha512-bq+F7nyc65sKpZGT09dY0S0QrOnQtuDVIfyTGQ8uuvtMIF7oHp6CEP3mouN0rrnYF3Jqo6Ke0BfU/5wASZue1w==, - } - engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } - peerDependencies: - unrs-resolver: ^1.0.0 - peerDependenciesMeta: - unrs-resolver: - optional: true - - eslint-json-compat-utils@0.2.1: - resolution: - { - integrity: sha512-YzEodbDyW8DX8bImKhAcCeu/L31Dd/70Bidx2Qex9OFUtgzXLqtfWL4Hr5fM/aCCB8QUZLuJur0S9k6UfgFkfg==, - } - engines: { node: ">=12" } - peerDependencies: - "@eslint/json": "*" - eslint: "*" - jsonc-eslint-parser: ^2.4.0 - peerDependenciesMeta: - "@eslint/json": - optional: true - - eslint-plugin-antfu@3.1.1: - resolution: - { - integrity: sha512-7Q+NhwLfHJFvopI2HBZbSxWXngTwBLKxW1AGXLr2lEGxcEIK/AsDs8pn8fvIizl5aZjBbVbVK5ujmMpBe4Tvdg==, - } - peerDependencies: - eslint: "*" - - eslint-plugin-command@3.3.1: - resolution: - { - integrity: sha512-fBVTXQ2y48TVLT0+4A6PFINp7GcdIailHAXbvPBixE7x+YpYnNQhFZxTdvnb+aWk+COgNebQKen/7m4dmgyWAw==, - } - peerDependencies: - eslint: "*" - - eslint-plugin-de-morgan@1.3.0: - resolution: - { - integrity: sha512-UHCQ4XyDaEeSmk7lI0O1TEcsSBkvdyY8FV4H9TN2DvBfKV45SJR/qZ/rYwzt8JSBIbMXy8S0vNBhzngnpIreJw==, - } - engines: { node: ^18.0.0 || >=20.0.0 } - peerDependencies: - eslint: ">=8.0.0" - - eslint-plugin-es-x@7.8.0: - resolution: - { - integrity: sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==, - } - engines: { node: ^14.18.0 || >=16.0.0 } - peerDependencies: - eslint: ">=8" - - eslint-plugin-import-x@4.15.2: - resolution: - { - integrity: sha512-J5gx7sN6DTm0LRT//eP3rVVQ2Yi4hrX0B+DbWxa5er8PZ6JjLo9GUBwogIFvEDdwJaSqZplpQT+haK/cXhb7VQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - peerDependencies: - "@typescript-eslint/utils": ^8.0.0 - eslint: ^8.57.0 || ^9.0.0 - eslint-import-resolver-node: "*" - peerDependenciesMeta: - "@typescript-eslint/utils": - optional: true - eslint-import-resolver-node: - optional: true - - eslint-plugin-jsdoc@51.2.1: - resolution: - { - integrity: sha512-iE2qpG/kaA9xXfEcTNSsxNvH5O8+o38VBGLwl2oZisQaM1JRGftTLJAGQrj7YZjSkp3n9VCrNTjOpo3ONhTApQ==, - } - engines: { node: ">=20.11.0" } - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 - - eslint-plugin-jsonc@2.20.1: - resolution: - { - integrity: sha512-gUzIwQHXx7ZPypUoadcyRi4WbHW2TPixDr0kqQ4miuJBU0emJmyGTlnaT3Og9X2a8R1CDayN9BFSq5weGWbTng==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } - peerDependencies: - eslint: ">=6.0.0" - - eslint-plugin-n@17.20.0: - resolution: - { - integrity: sha512-IRSoatgB/NQJZG5EeTbv/iAx1byOGdbbyhQrNvWdCfTnmPxUT0ao9/eGOeG7ljD8wJBsxwE8f6tES5Db0FRKEw==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - peerDependencies: - eslint: ">=8.23.0" - - eslint-plugin-perfectionist@4.15.0: - resolution: - { - integrity: sha512-pC7PgoXyDnEXe14xvRUhBII8A3zRgggKqJFx2a82fjrItDs1BSI7zdZnQtM2yQvcyod6/ujmzb7ejKPx8lZTnw==, - } - engines: { node: ^18.0.0 || >=20.0.0 } - peerDependencies: - eslint: ">=8.45.0" - - eslint-plugin-pnpm@0.3.1: - resolution: - { - integrity: sha512-vi5iHoELIAlBbX4AW8ZGzU3tUnfxuXhC/NKo3qRcI5o9igbz6zJUqSlQ03bPeMqWIGTPatZnbWsNR1RnlNERNQ==, - } - peerDependencies: - eslint: ^9.0.0 - - eslint-plugin-prettier@5.5.0: - resolution: - { - integrity: sha512-8qsOYwkkGrahrgoUv76NZi23koqXOGiiEzXMrT8Q7VcYaUISR+5MorIUxfWqYXN0fN/31WbSrxCxFkVQ43wwrA==, - } - engines: { node: ^14.18.0 || >=16.0.0 } - peerDependencies: - "@types/eslint": ">=8.0.0" - eslint: ">=8.0.0" - eslint-config-prettier: ">= 7.0.0 <10.0.0 || >=10.1.0" - prettier: ">=3.0.0" - peerDependenciesMeta: - "@types/eslint": - optional: true - eslint-config-prettier: - optional: true - - eslint-plugin-regexp@2.9.0: - resolution: - { - integrity: sha512-9WqJMnOq8VlE/cK+YAo9C9YHhkOtcEtEk9d12a+H7OSZFwlpI6stiHmYPGa2VE0QhTzodJyhlyprUaXDZLgHBw==, - } - engines: { node: ^18 || >=20 } - peerDependencies: - eslint: ">=8.44.0" - - eslint-plugin-sxzz@0.3.0: - resolution: - { - integrity: sha512-Zf5ubmi+oPqIYAZFJJYvUN7+cyuyShfRpPCLR245PYjYguay/eLDo1c671V8DJqURzk2B6rhhGJAfA9oJDdApA==, - } - engines: { node: ">=20.18.0" } - peerDependencies: - eslint: "*" - - eslint-plugin-unicorn@59.0.1: - resolution: - { - integrity: sha512-EtNXYuWPUmkgSU2E7Ttn57LbRREQesIP1BiLn7OZLKodopKfDXfBUkC/0j6mpw2JExwf43Uf3qLSvrSvppgy8Q==, - } - engines: { node: ^18.20.0 || ^20.10.0 || >=21.0.0 } - peerDependencies: - eslint: ">=9.22.0" - - eslint-plugin-unused-imports@4.1.4: - resolution: - { - integrity: sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ==, - } - peerDependencies: - "@typescript-eslint/eslint-plugin": ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0 - eslint: ^9.0.0 || ^8.0.0 - peerDependenciesMeta: - "@typescript-eslint/eslint-plugin": - optional: true - - eslint-plugin-vue@10.2.0: - resolution: - { - integrity: sha512-tl9s+KN3z0hN2b8fV2xSs5ytGl7Esk1oSCxULLwFcdaElhZ8btYYZFrWxvh4En+czrSDtuLCeCOGa8HhEZuBdQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - vue-eslint-parser: ^10.0.0 - - eslint-plugin-yml@1.18.0: - resolution: - { - integrity: sha512-9NtbhHRN2NJa/s3uHchO3qVVZw0vyOIvWlXWGaKCr/6l3Go62wsvJK5byiI6ZoYztDsow4GnS69BZD3GnqH3hA==, - } - engines: { node: ^14.17.0 || >=16.0.0 } - peerDependencies: - eslint: ">=6.0.0" - - eslint-scope@8.4.0: - resolution: - { - integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - eslint-visitor-keys@3.4.3: - resolution: - { - integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } - - eslint-visitor-keys@4.2.1: - resolution: - { - integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - eslint@9.29.0: - resolution: - { - integrity: sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - hasBin: true - peerDependencies: - jiti: "*" - peerDependenciesMeta: - jiti: - optional: true - - espree@10.4.0: - resolution: - { - integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - - espree@9.6.1: - resolution: - { - integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } - - esquery@1.6.0: - resolution: - { - integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==, - } - engines: { node: ">=0.10" } - - esrecurse@4.3.0: - resolution: - { - integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==, - } - engines: { node: ">=4.0" } - - estraverse@5.3.0: - resolution: - { - integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==, - } - engines: { node: ">=4.0" } + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} estree-walker@3.0.3: - resolution: - { - integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==, - } - - esutils@2.0.3: - resolution: - { - integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} expect-type@1.2.1: - resolution: - { - integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==, - } - engines: { node: ">=12.0.0" } + resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} + engines: {node: '>=12.0.0'} exsolve@1.0.7: - resolution: - { - integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==, - } + resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} - fast-deep-equal@3.1.3: - resolution: - { - integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, - } - - fast-diff@1.3.0: - resolution: - { - integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==, - } - - fast-glob@3.3.3: - resolution: - { - integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==, - } - engines: { node: ">=8.6.0" } - - fast-json-stable-stringify@2.1.0: - resolution: - { - integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==, - } - - fast-levenshtein@2.0.6: - resolution: - { - integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==, - } - - fastq@1.19.1: - resolution: - { - integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==, - } - - fault@2.0.1: - resolution: - { - integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==, - } + fast-redact@3.5.0: + resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} + engines: {node: '>=6'} fdir@6.4.6: - resolution: - { - integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==, - } + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} peerDependencies: picomatch: ^3 || ^4 peerDependenciesMeta: picomatch: optional: true - file-entry-cache@8.0.0: - resolution: - { - integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==, - } - engines: { node: ">=16.0.0" } - - fill-range@7.1.1: - resolution: - { - integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==, - } - engines: { node: ">=8" } - - find-up-simple@1.0.1: - resolution: - { - integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==, - } - engines: { node: ">=18" } - - find-up@5.0.0: - resolution: - { - integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==, - } - engines: { node: ">=10" } - - flat-cache@4.0.1: - resolution: - { - integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==, - } - engines: { node: ">=16" } - - flatted@3.3.3: - resolution: - { - integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==, - } - - format@0.2.2: - resolution: - { - integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==, - } - engines: { node: ">=0.4.x" } - fsevents@2.3.3: - resolution: - { - integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, - } - engines: { node: ^8.16.0 || ^10.6.0 || >=11.0.0 } + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] get-tsconfig@4.10.1: - resolution: - { - integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==, - } + resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} giget@2.0.0: - resolution: - { - integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==, - } + resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} hasBin: true - github-slugger@2.0.0: - resolution: - { - integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==, - } - - glob-parent@5.1.2: - resolution: - { - integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==, - } - engines: { node: ">= 6" } - - glob-parent@6.0.2: - resolution: - { - integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==, - } - engines: { node: ">=10.13.0" } - - globals@14.0.0: - resolution: - { - integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==, - } - engines: { node: ">=18" } - - globals@15.15.0: - resolution: - { - integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==, - } - engines: { node: ">=18" } - - globals@16.2.0: - resolution: - { - integrity: sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==, - } - engines: { node: ">=18" } - - graceful-fs@4.2.11: - resolution: - { - integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==, - } - - graphemer@1.4.0: - resolution: - { - integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==, - } - happy-dom@17.6.3: - resolution: - { - integrity: sha512-UVIHeVhxmxedbWPCfgS55Jg2rDfwf2BCKeylcPSqazLz5w3Kri7Q4xdBJubsr/+VUzFLh0VjIvh13RaDA2/Xug==, - } - engines: { node: ">=20.0.0" } - - has-flag@4.0.0: - resolution: - { - integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-UVIHeVhxmxedbWPCfgS55Jg2rDfwf2BCKeylcPSqazLz5w3Kri7Q4xdBJubsr/+VUzFLh0VjIvh13RaDA2/Xug==} + engines: {node: '>=20.0.0'} hookable@5.5.3: - resolution: - { - integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==, - } - - ignore@5.3.2: - resolution: - { - integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==, - } - engines: { node: ">= 4" } - - ignore@7.0.5: - resolution: - { - integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==, - } - engines: { node: ">= 4" } - - import-fresh@3.3.1: - resolution: - { - integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==, - } - engines: { node: ">=6" } - - imurmurhash@0.1.4: - resolution: - { - integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==, - } - engines: { node: ">=0.8.19" } - - indent-string@5.0.0: - resolution: - { - integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==, - } - engines: { node: ">=12" } - - is-builtin-module@5.0.0: - resolution: - { - integrity: sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA==, - } - engines: { node: ">=18.20" } - - is-extglob@2.1.1: - resolution: - { - integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==, - } - engines: { node: ">=0.10.0" } - - is-glob@4.0.3: - resolution: - { - integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==, - } - engines: { node: ">=0.10.0" } - - is-number@7.0.0: - resolution: - { - integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==, - } - engines: { node: ">=0.12.0" } - - isexe@2.0.0: - resolution: - { - integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, - } + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} jiti@2.4.2: - resolution: - { - integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==, - } + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} hasBin: true js-tokens@9.0.1: - resolution: - { - integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==, - } - - js-yaml@4.1.0: - resolution: - { - integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==, - } - hasBin: true - - jsdoc-type-pratt-parser@4.1.0: - resolution: - { - integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==, - } - engines: { node: ">=12.0.0" } - - jsesc@3.0.2: - resolution: - { - integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==, - } - engines: { node: ">=6" } - hasBin: true + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} jsesc@3.1.0: - resolution: - { - integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==, - } - engines: { node: ">=6" } + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} hasBin: true - json-buffer@3.0.1: - resolution: - { - integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==, - } - - json-schema-traverse@0.4.1: - resolution: - { - integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==, - } - - json-stable-stringify-without-jsonify@1.0.1: - resolution: - { - integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==, - } - - jsonc-eslint-parser@2.4.0: - resolution: - { - integrity: sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==, - } - engines: { node: ^12.22.0 || ^14.17.0 || >=16.0.0 } - jsonc-parser@3.3.1: - resolution: - { - integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==, - } - - keyv@4.5.4: - resolution: - { - integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==, - } - - levn@0.4.1: - resolution: - { - integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==, - } - engines: { node: ">= 0.8.0" } - - local-pkg@1.1.1: - resolution: - { - integrity: sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==, - } - engines: { node: ">=14" } - - locate-path@6.0.0: - resolution: - { - integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==, - } - engines: { node: ">=10" } - - lodash.merge@4.6.2: - resolution: - { - integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==, - } - - lodash@4.17.21: - resolution: - { - integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==, - } - - longest-streak@3.1.0: - resolution: - { - integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==, - } + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} loupe@3.1.4: - resolution: - { - integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==, - } + resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==} magic-string@0.30.17: - resolution: - { - integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==, - } - - markdown-table@3.0.4: - resolution: - { - integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==, - } - - mdast-util-find-and-replace@3.0.2: - resolution: - { - integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==, - } - - mdast-util-from-markdown@2.0.2: - resolution: - { - integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==, - } - - mdast-util-frontmatter@2.0.1: - resolution: - { - integrity: sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==, - } - - mdast-util-gfm-autolink-literal@2.0.1: - resolution: - { - integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==, - } - - mdast-util-gfm-footnote@2.1.0: - resolution: - { - integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==, - } - - mdast-util-gfm-strikethrough@2.0.0: - resolution: - { - integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==, - } - - mdast-util-gfm-table@2.0.0: - resolution: - { - integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==, - } - - mdast-util-gfm-task-list-item@2.0.0: - resolution: - { - integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==, - } - - mdast-util-gfm@3.1.0: - resolution: - { - integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==, - } - - mdast-util-phrasing@4.1.0: - resolution: - { - integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==, - } - - mdast-util-to-markdown@2.1.2: - resolution: - { - integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==, - } - - mdast-util-to-string@4.0.0: - resolution: - { - integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==, - } - - merge2@1.4.1: - resolution: - { - integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==, - } - engines: { node: ">= 8" } - - micromark-core-commonmark@2.0.3: - resolution: - { - integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==, - } - - micromark-extension-frontmatter@2.0.0: - resolution: - { - integrity: sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==, - } - - micromark-extension-gfm-autolink-literal@2.1.0: - resolution: - { - integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==, - } - - micromark-extension-gfm-footnote@2.1.0: - resolution: - { - integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==, - } - - micromark-extension-gfm-strikethrough@2.1.0: - resolution: - { - integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==, - } - - micromark-extension-gfm-table@2.1.1: - resolution: - { - integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==, - } - - micromark-extension-gfm-tagfilter@2.0.0: - resolution: - { - integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==, - } - - micromark-extension-gfm-task-list-item@2.1.0: - resolution: - { - integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==, - } - - micromark-extension-gfm@3.0.0: - resolution: - { - integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==, - } - - micromark-factory-destination@2.0.1: - resolution: - { - integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==, - } - - micromark-factory-label@2.0.1: - resolution: - { - integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==, - } - - micromark-factory-space@2.0.1: - resolution: - { - integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==, - } - - micromark-factory-title@2.0.1: - resolution: - { - integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==, - } - - micromark-factory-whitespace@2.0.1: - resolution: - { - integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==, - } - - micromark-util-character@2.1.1: - resolution: - { - integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==, - } - - micromark-util-chunked@2.0.1: - resolution: - { - integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==, - } - - micromark-util-classify-character@2.0.1: - resolution: - { - integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==, - } - - micromark-util-combine-extensions@2.0.1: - resolution: - { - integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==, - } - - micromark-util-decode-numeric-character-reference@2.0.2: - resolution: - { - integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==, - } - - micromark-util-decode-string@2.0.1: - resolution: - { - integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==, - } - - micromark-util-encode@2.0.1: - resolution: - { - integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==, - } - - micromark-util-html-tag-name@2.0.1: - resolution: - { - integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==, - } - - micromark-util-normalize-identifier@2.0.1: - resolution: - { - integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==, - } - - micromark-util-resolve-all@2.0.1: - resolution: - { - integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==, - } - - micromark-util-sanitize-uri@2.0.1: - resolution: - { - integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==, - } - - micromark-util-subtokenize@2.1.0: - resolution: - { - integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==, - } - - micromark-util-symbol@2.0.1: - resolution: - { - integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==, - } - - micromark-util-types@2.0.2: - resolution: - { - integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==, - } - - micromark@4.0.2: - resolution: - { - integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==, - } - - micromatch@4.0.8: - resolution: - { - integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==, - } - engines: { node: ">=8.6" } - - min-indent@1.0.1: - resolution: - { - integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==, - } - engines: { node: ">=4" } - - minimatch@10.0.3: - resolution: - { - integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==, - } - engines: { node: 20 || >=22 } - - minimatch@3.1.2: - resolution: - { - integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==, - } - - minimatch@9.0.5: - resolution: - { - integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==, - } - engines: { node: ">=16 || 14 >=14.17" } - - mlly@1.7.4: - resolution: - { - integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==, - } + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} ms@2.1.3: - resolution: - { - integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, - } + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} nanoid@3.3.11: - resolution: - { - integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==, - } - engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - napi-postinstall@0.2.4: - resolution: - { - integrity: sha512-ZEzHJwBhZ8qQSbknHqYcdtQVr8zUgGyM/q6h6qAyhtyVMNrSgDhrC4disf03dYW0e+czXyLnZINnCTEkWy0eJg==, - } - engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } - hasBin: true - - natural-compare@1.4.0: - resolution: - { - integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, - } - - natural-orderby@5.0.0: - resolution: - { - integrity: sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg==, - } - engines: { node: ">=18" } - node-fetch-native@1.6.6: - resolution: - { - integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==, - } - - node-releases@2.0.19: - resolution: - { - integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==, - } - - nth-check@2.1.1: - resolution: - { - integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==, - } + resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==} nypm@0.6.0: - resolution: - { - integrity: sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg==, - } - engines: { node: ^14.16.0 || >=16.10.0 } + resolution: {integrity: sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg==} + engines: {node: ^14.16.0 || >=16.10.0} hasBin: true ohash@2.0.11: - resolution: - { - integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==, - } + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} - optionator@0.9.4: - resolution: - { - integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==, - } - engines: { node: ">= 0.8.0" } - - p-limit@3.1.0: - resolution: - { - integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==, - } - engines: { node: ">=10" } - - p-locate@5.0.0: - resolution: - { - integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==, - } - engines: { node: ">=10" } + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} package-manager-detector@1.3.0: - resolution: - { - integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==, - } - - parent-module@1.0.1: - resolution: - { - integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==, - } - engines: { node: ">=6" } - - parse-imports-exports@0.2.4: - resolution: - { - integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==, - } - - parse-statements@1.0.11: - resolution: - { - integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==, - } - - path-exists@4.0.0: - resolution: - { - integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==, - } - engines: { node: ">=8" } - - path-key@3.1.1: - resolution: - { - integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} pathe@2.0.3: - resolution: - { - integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==, - } + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} pathval@2.0.0: - resolution: - { - integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==, - } - engines: { node: ">= 14.16" } + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} perfect-debounce@1.0.0: - resolution: - { - integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==, - } + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} picocolors@1.1.1: - resolution: - { - integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, - } - - picomatch@2.3.1: - resolution: - { - integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==, - } - engines: { node: ">=8.6" } + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} picomatch@4.0.2: - resolution: - { - integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} - pkg-types@1.3.1: - resolution: - { - integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==, - } + pino-abstract-transport@2.0.0: + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} + + pino-std-serializers@7.0.0: + resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} + + pino@9.7.0: + resolution: {integrity: sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==} + hasBin: true pkg-types@2.1.0: - resolution: - { - integrity: sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==, - } - - pluralize@8.0.0: - resolution: - { - integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==, - } - engines: { node: ">=4" } - - pnpm-workspace-yaml@0.3.1: - resolution: - { - integrity: sha512-3nW5RLmREmZ8Pm8MbPsO2RM+99RRjYd25ynj3NV0cFsN7CcEl4sDFzgoFmSyduFwxFQ2Qbu3y2UdCh6HlyUOeA==, - } - - postcss-selector-parser@6.1.2: - resolution: - { - integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==, - } - engines: { node: ">=4" } + resolution: {integrity: sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==} postcss@8.5.6: - resolution: - { - integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==, - } - engines: { node: ^10 || ^12 || >=14 } + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} - prelude-ls@1.2.1: - resolution: - { - integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==, - } - engines: { node: ">= 0.8.0" } + posthog-node@5.1.1: + resolution: {integrity: sha512-6VISkNdxO24ehXiDA4dugyCSIV7lpGVaEu5kn/dlAj+SJ1lgcDru9PQ8p/+GSXsXVxohd1t7kHL2JKc9NoGb0w==} + engines: {node: '>=20'} - prettier-linter-helpers@1.0.0: - resolution: - { - integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==, - } - engines: { node: ">=6.0.0" } - - prettier@3.6.0: - resolution: - { - integrity: sha512-ujSB9uXHJKzM/2GBuE0hBOUgC77CN3Bnpqa+g80bkv3T3A93wL/xlzDATHhnhkzifz/UE2SNOvmbTz5hSkDlHw==, - } - engines: { node: ">=14" } - hasBin: true - - punycode@2.3.1: - resolution: - { - integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==, - } - engines: { node: ">=6" } + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} quansync@0.2.10: - resolution: - { - integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==, - } + resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} - queue-microtask@1.2.3: - resolution: - { - integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, - } + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} rc9@2.1.2: - resolution: - { - integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==, - } + resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} readdirp@4.1.2: - resolution: - { - integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==, - } - engines: { node: ">= 14.18.0" } + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} - refa@0.12.1: - resolution: - { - integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==, - } - engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } - - regexp-ast-analysis@0.7.1: - resolution: - { - integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==, - } - engines: { node: ^12.0.0 || ^14.0.0 || >=16.0.0 } - - regexp-tree@0.1.27: - resolution: - { - integrity: sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==, - } - hasBin: true - - regjsparser@0.12.0: - resolution: - { - integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==, - } - hasBin: true - - resolve-from@4.0.0: - resolution: - { - integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==, - } - engines: { node: ">=4" } + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} resolve-pkg-maps@1.0.0: - resolution: - { - integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==, - } - - reusify@1.1.0: - resolution: - { - integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==, - } - engines: { iojs: ">=1.0.0", node: ">=0.10.0" } + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} rolldown-plugin-dts@0.13.12: - resolution: - { - integrity: sha512-4QdQQfMOWNDLhmvoG3USAUpCm75dgwWk3IrK6SV9Fw2U5uwcSE7oQTqEcmsUGEBsNxZ58+gtM1oX38MMf0g5PA==, - } - engines: { node: ">=20.18.0" } + resolution: {integrity: sha512-4QdQQfMOWNDLhmvoG3USAUpCm75dgwWk3IrK6SV9Fw2U5uwcSE7oQTqEcmsUGEBsNxZ58+gtM1oX38MMf0g5PA==} + engines: {node: '>=20.18.0'} peerDependencies: - "@typescript/native-preview": ">=7.0.0-dev.20250601.1" + '@typescript/native-preview': '>=7.0.0-dev.20250601.1' rolldown: ^1.0.0-beta.9 typescript: ^5.0.0 vue-tsc: ~2.2.0 peerDependenciesMeta: - "@typescript/native-preview": + '@typescript/native-preview': optional: true typescript: optional: true @@ -2993,227 +810,82 @@ packages: optional: true rolldown@1.0.0-beta.9: - resolution: - { - integrity: sha512-ZgZky52n6iF0UainGKjptKGrOG4Con2S5sdc4C4y2Oj25D5PHAY8Y8E5f3M2TSd/zlhQs574JlMeTe3vREczSg==, - } + resolution: {integrity: sha512-ZgZky52n6iF0UainGKjptKGrOG4Con2S5sdc4C4y2Oj25D5PHAY8Y8E5f3M2TSd/zlhQs574JlMeTe3vREczSg==} hasBin: true peerDependencies: - "@oxc-project/runtime": 0.70.0 + '@oxc-project/runtime': 0.70.0 peerDependenciesMeta: - "@oxc-project/runtime": + '@oxc-project/runtime': optional: true rollup@4.44.0: - resolution: - { - integrity: sha512-qHcdEzLCiktQIfwBq420pn2dP+30uzqYxv9ETm91wdt2R9AFcWfjNAmje4NWlnCIQ5RMTzVf0ZyisOKqHR6RwA==, - } - engines: { node: ">=18.0.0", npm: ">=8.0.0" } + resolution: {integrity: sha512-qHcdEzLCiktQIfwBq420pn2dP+30uzqYxv9ETm91wdt2R9AFcWfjNAmje4NWlnCIQ5RMTzVf0ZyisOKqHR6RwA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - run-parallel@1.2.0: - resolution: - { - integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==, - } - - scslre@0.3.0: - resolution: - { - integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==, - } - engines: { node: ^14.0.0 || >=16.0.0 } + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} semver@7.7.2: - resolution: - { - integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==, - } - engines: { node: ">=10" } + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} hasBin: true - shebang-command@2.0.0: - resolution: - { - integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==, - } - engines: { node: ">=8" } - - shebang-regex@3.0.0: - resolution: - { - integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==, - } - engines: { node: ">=8" } - siginfo@2.0.0: - resolution: - { - integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==, - } + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + sonic-boom@4.2.0: + resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} source-map-js@1.2.1: - resolution: - { - integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==, - } - engines: { node: ">=0.10.0" } + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} - spdx-exceptions@2.5.0: - resolution: - { - integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==, - } - - spdx-expression-parse@4.0.0: - resolution: - { - integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==, - } - - spdx-license-ids@3.0.21: - resolution: - { - integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==, - } - - stable-hash-x@0.1.1: - resolution: - { - integrity: sha512-l0x1D6vhnsNUGPFVDx45eif0y6eedVC8nm5uACTrVFJFtl2mLRW17aWtVyxFCpn5t94VUPkjU8vSLwIuwwqtJQ==, - } - engines: { node: ">=12.0.0" } + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} stackback@0.0.2: - resolution: - { - integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==, - } + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} std-env@3.9.0: - resolution: - { - integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==, - } - - strip-indent@4.0.0: - resolution: - { - integrity: sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==, - } - engines: { node: ">=12" } - - strip-json-comments@3.1.1: - resolution: - { - integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} strip-literal@3.0.0: - resolution: - { - integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==, - } + resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} - supports-color@7.2.0: - resolution: - { - integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==, - } - engines: { node: ">=8" } - - synckit@0.11.8: - resolution: - { - integrity: sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==, - } - engines: { node: ^14.18.0 || >=16.0.0 } - - tapable@2.2.2: - resolution: - { - integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==, - } - engines: { node: ">=6" } + thread-stream@3.1.0: + resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} tinybench@2.9.0: - resolution: - { - integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==, - } + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} tinyexec@0.3.2: - resolution: - { - integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==, - } + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} tinyexec@1.0.1: - resolution: - { - integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==, - } + resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} tinyglobby@0.2.14: - resolution: - { - integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==, - } - engines: { node: ">=12.0.0" } + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} tinypool@1.1.1: - resolution: - { - integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==, - } - engines: { node: ^18.0.0 || >=20.0.0 } + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} tinyrainbow@2.0.0: - resolution: - { - integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==, - } - engines: { node: ">=14.0.0" } + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} tinyspy@4.0.3: - resolution: - { - integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==, - } - engines: { node: ">=14.0.0" } - - to-regex-range@5.0.1: - resolution: - { - integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==, - } - engines: { node: ">=8.0" } - - ts-api-utils@2.1.0: - resolution: - { - integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==, - } - engines: { node: ">=18.12" } - peerDependencies: - typescript: ">=4.8.4" - - ts-declaration-location@1.0.7: - resolution: - { - integrity: sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==, - } - peerDependencies: - typescript: ">=4.0.0" + resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} + engines: {node: '>=14.0.0'} tsdown@0.11.13: - resolution: - { - integrity: sha512-VSfoNm8MJXFdg7PJ4p2javgjMRiQQHpkP9N3iBBTrmCixcT6YZ9ZtqYMW3NDHczqR0C0Qnur1HMQr1ZfZcmrng==, - } - engines: { node: ">=18.0.0" } + resolution: {integrity: sha512-VSfoNm8MJXFdg7PJ4p2javgjMRiQQHpkP9N3iBBTrmCixcT6YZ9ZtqYMW3NDHczqR0C0Qnur1HMQr1ZfZcmrng==} + engines: {node: '>=18.0.0'} hasBin: true peerDependencies: publint: ^0.3.0 @@ -3231,142 +903,51 @@ packages: optional: true tslib@2.8.1: - resolution: - { - integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==, - } + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} tsx@4.20.3: - resolution: - { - integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==, - } - engines: { node: ">=18.0.0" } + resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==} + engines: {node: '>=18.0.0'} hasBin: true - type-check@0.4.0: - resolution: - { - integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==, - } - engines: { node: ">= 0.8.0" } - - typescript-eslint@8.34.1: - resolution: - { - integrity: sha512-XjS+b6Vg9oT1BaIUfkW3M3LvqZE++rbzAMEHuccCfO/YkP43ha6w3jTEMilQxMF92nVOYCcdjv1ZUhAa1D/0ow==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - typescript@5.8.3: - resolution: - { - integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==, - } - engines: { node: ">=14.17" } + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} hasBin: true - ufo@1.6.1: - resolution: - { - integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==, - } - unconfig@7.3.2: - resolution: - { - integrity: sha512-nqG5NNL2wFVGZ0NA/aCFw0oJ2pxSf1lwg4Z5ill8wd7K4KX/rQbHlwbh+bjctXL5Ly1xtzHenHGOK0b+lG6JVg==, - } + resolution: {integrity: sha512-nqG5NNL2wFVGZ0NA/aCFw0oJ2pxSf1lwg4Z5ill8wd7K4KX/rQbHlwbh+bjctXL5Ly1xtzHenHGOK0b+lG6JVg==} undici-types@6.21.0: - resolution: - { - integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==, - } + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - unist-util-is@6.0.0: - resolution: - { - integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==, - } - - unist-util-stringify-position@4.0.0: - resolution: - { - integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==, - } - - unist-util-visit-parents@6.0.1: - resolution: - { - integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==, - } - - unist-util-visit@5.0.0: - resolution: - { - integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==, - } - - unrs-resolver@1.9.1: - resolution: - { - integrity: sha512-4AZVxP05JGN6DwqIkSP4VKLOcwQa5l37SWHF/ahcuqBMbfxbpN1L1QKafEhWCziHhzKex9H/AR09H0OuVyU+9g==, - } - - update-browserslist-db@1.1.3: - resolution: - { - integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==, - } + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} hasBin: true - peerDependencies: - browserslist: ">= 4.21.0" - - uri-js@4.4.1: - resolution: - { - integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, - } - - util-deprecate@1.0.2: - resolution: - { - integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==, - } vite-node@3.2.4: - resolution: - { - integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==, - } - engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true vite@6.3.5: - resolution: - { - integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==, - } - engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } + resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: - "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 - jiti: ">=1.21.0" - less: "*" + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' lightningcss: ^1.21.0 - sass: "*" - sass-embedded: "*" - stylus: "*" - sugarss: "*" + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' terser: ^5.16.0 tsx: ^4.8.1 yaml: ^2.4.2 peerDependenciesMeta: - "@types/node": + '@types/node': optional: true jiti: optional: true @@ -3390,816 +971,418 @@ packages: optional: true vitest@3.2.4: - resolution: - { - integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==, - } - engines: { node: ^18.0.0 || ^20.0.0 || >=22.0.0 } + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} hasBin: true peerDependencies: - "@edge-runtime/vm": "*" - "@types/debug": ^4.1.12 - "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 - "@vitest/browser": 3.2.4 - "@vitest/ui": 3.2.4 - happy-dom: "*" - jsdom: "*" + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' peerDependenciesMeta: - "@edge-runtime/vm": + '@edge-runtime/vm': optional: true - "@types/debug": + '@types/debug': optional: true - "@types/node": + '@types/node': optional: true - "@vitest/browser": + '@vitest/browser': optional: true - "@vitest/ui": + '@vitest/ui': optional: true happy-dom: optional: true jsdom: optional: true - vue-eslint-parser@10.1.3: - resolution: - { - integrity: sha512-dbCBnd2e02dYWsXoqX5yKUZlOt+ExIpq7hmHKPb5ZqKcjf++Eo0hMseFTZMLKThrUk61m+Uv6A2YSBve6ZvuDQ==, - } - engines: { node: ^18.18.0 || ^20.9.0 || >=21.1.0 } - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - webidl-conversions@7.0.0: - resolution: - { - integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==, - } - engines: { node: ">=12" } + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} whatwg-mimetype@3.0.0: - resolution: - { - integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==, - } - engines: { node: ">=12" } - - which@2.0.2: - resolution: - { - integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==, - } - engines: { node: ">= 8" } - hasBin: true + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} why-is-node-running@2.3.0: - resolution: - { - integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==, - } - engines: { node: ">=8" } + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} hasBin: true - word-wrap@1.2.5: - resolution: - { - integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==, - } - engines: { node: ">=0.10.0" } - - xml-name-validator@4.0.0: - resolution: - { - integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==, - } - engines: { node: ">=12" } - - yaml-eslint-parser@1.3.0: - resolution: - { - integrity: sha512-E/+VitOorXSLiAqtTd7Yqax0/pAS3xaYMP+AUUJGOK1OZG3rhcj9fcJOM5HJ2VrP1FrStVCWr1muTfQCdj4tAA==, - } - engines: { node: ^14.17.0 || >=16.0.0 } - yaml@2.8.0: - resolution: - { - integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==, - } - engines: { node: ">= 14.6" } + resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==} + engines: {node: '>= 14.6'} hasBin: true - yocto-queue@0.1.0: - resolution: - { - integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==, - } - engines: { node: ">=10" } - - zwitch@2.0.4: - resolution: - { - integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==, - } - snapshots: - "@babel/generator@7.27.5": + + '@babel/generator@7.27.5': dependencies: - "@babel/parser": 7.27.5 - "@babel/types": 7.27.6 - "@jridgewell/gen-mapping": 0.3.8 - "@jridgewell/trace-mapping": 0.3.25 + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.1.0 - "@babel/helper-string-parser@7.27.1": {} + '@babel/helper-string-parser@7.27.1': {} - "@babel/helper-validator-identifier@7.27.1": {} + '@babel/helper-validator-identifier@7.27.1': {} - "@babel/parser@7.27.5": + '@babel/parser@7.27.5': dependencies: - "@babel/types": 7.27.6 + '@babel/types': 7.27.6 - "@babel/types@7.27.6": + '@babel/types@7.27.6': dependencies: - "@babel/helper-string-parser": 7.27.1 - "@babel/helper-validator-identifier": 7.27.1 + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 - "@emnapi/core@1.4.3": - dependencies: - "@emnapi/wasi-threads": 1.0.2 - tslib: 2.8.1 - optional: true - - "@emnapi/runtime@1.4.3": - dependencies: - tslib: 2.8.1 - optional: true - - "@emnapi/wasi-threads@1.0.2": - dependencies: - tslib: 2.8.1 - optional: true - - "@es-joy/jsdoccomment@0.50.2": - dependencies: - "@types/estree": 1.0.8 - "@typescript-eslint/types": 8.34.1 - comment-parser: 1.4.1 - esquery: 1.6.0 - jsdoc-type-pratt-parser: 4.1.0 - - "@es-joy/jsdoccomment@0.52.0": - dependencies: - "@types/estree": 1.0.8 - "@typescript-eslint/types": 8.34.1 - comment-parser: 1.4.1 - esquery: 1.6.0 - jsdoc-type-pratt-parser: 4.1.0 - - "@esbuild/aix-ppc64@0.25.5": - optional: true - - "@esbuild/android-arm64@0.25.5": - optional: true - - "@esbuild/android-arm@0.25.5": - optional: true - - "@esbuild/android-x64@0.25.5": - optional: true - - "@esbuild/darwin-arm64@0.25.5": - optional: true - - "@esbuild/darwin-x64@0.25.5": - optional: true - - "@esbuild/freebsd-arm64@0.25.5": - optional: true - - "@esbuild/freebsd-x64@0.25.5": - optional: true - - "@esbuild/linux-arm64@0.25.5": - optional: true - - "@esbuild/linux-arm@0.25.5": - optional: true - - "@esbuild/linux-ia32@0.25.5": - optional: true - - "@esbuild/linux-loong64@0.25.5": - optional: true - - "@esbuild/linux-mips64el@0.25.5": - optional: true - - "@esbuild/linux-ppc64@0.25.5": - optional: true - - "@esbuild/linux-riscv64@0.25.5": - optional: true - - "@esbuild/linux-s390x@0.25.5": - optional: true - - "@esbuild/linux-x64@0.25.5": - optional: true - - "@esbuild/netbsd-arm64@0.25.5": - optional: true - - "@esbuild/netbsd-x64@0.25.5": - optional: true - - "@esbuild/openbsd-arm64@0.25.5": - optional: true - - "@esbuild/openbsd-x64@0.25.5": - optional: true - - "@esbuild/sunos-x64@0.25.5": - optional: true - - "@esbuild/win32-arm64@0.25.5": - optional: true - - "@esbuild/win32-ia32@0.25.5": - optional: true - - "@esbuild/win32-x64@0.25.5": - optional: true - - "@eslint-community/eslint-plugin-eslint-comments@4.5.0(eslint@9.29.0(jiti@2.4.2))": - dependencies: - escape-string-regexp: 4.0.0 - eslint: 9.29.0(jiti@2.4.2) - ignore: 5.3.2 - - "@eslint-community/eslint-utils@4.7.0(eslint@9.29.0(jiti@2.4.2))": - dependencies: - eslint: 9.29.0(jiti@2.4.2) - eslint-visitor-keys: 3.4.3 - - "@eslint-community/regexpp@4.12.1": {} - - "@eslint/compat@1.3.0(eslint@9.29.0(jiti@2.4.2))": + '@biomejs/biome@1.9.4': optionalDependencies: - eslint: 9.29.0(jiti@2.4.2) + '@biomejs/cli-darwin-arm64': 1.9.4 + '@biomejs/cli-darwin-x64': 1.9.4 + '@biomejs/cli-linux-arm64': 1.9.4 + '@biomejs/cli-linux-arm64-musl': 1.9.4 + '@biomejs/cli-linux-x64': 1.9.4 + '@biomejs/cli-linux-x64-musl': 1.9.4 + '@biomejs/cli-win32-arm64': 1.9.4 + '@biomejs/cli-win32-x64': 1.9.4 - "@eslint/config-array@0.20.1": - dependencies: - "@eslint/object-schema": 2.1.6 - debug: 4.4.1 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - - "@eslint/config-helpers@0.2.3": {} - - "@eslint/core@0.13.0": - dependencies: - "@types/json-schema": 7.0.15 - - "@eslint/core@0.14.0": - dependencies: - "@types/json-schema": 7.0.15 - - "@eslint/core@0.15.0": - dependencies: - "@types/json-schema": 7.0.15 - - "@eslint/eslintrc@3.3.1": - dependencies: - ajv: 6.12.6 - debug: 4.4.1 - espree: 10.4.0 - globals: 14.0.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - "@eslint/js@9.29.0": {} - - "@eslint/markdown@6.6.0": - dependencies: - "@eslint/core": 0.14.0 - "@eslint/plugin-kit": 0.3.2 - github-slugger: 2.0.0 - mdast-util-from-markdown: 2.0.2 - mdast-util-frontmatter: 2.0.1 - mdast-util-gfm: 3.1.0 - micromark-extension-frontmatter: 2.0.0 - micromark-extension-gfm: 3.0.0 - transitivePeerDependencies: - - supports-color - - "@eslint/object-schema@2.1.6": {} - - "@eslint/plugin-kit@0.2.8": - dependencies: - "@eslint/core": 0.13.0 - levn: 0.4.1 - - "@eslint/plugin-kit@0.3.2": - dependencies: - "@eslint/core": 0.15.0 - levn: 0.4.1 - - "@humanfs/core@0.19.1": {} - - "@humanfs/node@0.16.6": - dependencies: - "@humanfs/core": 0.19.1 - "@humanwhocodes/retry": 0.3.1 - - "@humanwhocodes/module-importer@1.0.1": {} - - "@humanwhocodes/retry@0.3.1": {} - - "@humanwhocodes/retry@0.4.3": {} - - "@isaacs/balanced-match@4.0.1": {} - - "@isaacs/brace-expansion@5.0.0": - dependencies: - "@isaacs/balanced-match": 4.0.1 - - "@jridgewell/gen-mapping@0.3.8": - dependencies: - "@jridgewell/set-array": 1.2.1 - "@jridgewell/sourcemap-codec": 1.5.0 - "@jridgewell/trace-mapping": 0.3.25 - - "@jridgewell/resolve-uri@3.1.2": {} - - "@jridgewell/set-array@1.2.1": {} - - "@jridgewell/sourcemap-codec@1.5.0": {} - - "@jridgewell/trace-mapping@0.3.25": - dependencies: - "@jridgewell/resolve-uri": 3.1.2 - "@jridgewell/sourcemap-codec": 1.5.0 - - "@napi-rs/wasm-runtime@0.2.11": - dependencies: - "@emnapi/core": 1.4.3 - "@emnapi/runtime": 1.4.3 - "@tybys/wasm-util": 0.9.0 + '@biomejs/cli-darwin-arm64@1.9.4': optional: true - "@nodelib/fs.scandir@2.1.5": + '@biomejs/cli-darwin-x64@1.9.4': + optional: true + + '@biomejs/cli-linux-arm64-musl@1.9.4': + optional: true + + '@biomejs/cli-linux-arm64@1.9.4': + optional: true + + '@biomejs/cli-linux-x64-musl@1.9.4': + optional: true + + '@biomejs/cli-linux-x64@1.9.4': + optional: true + + '@biomejs/cli-win32-arm64@1.9.4': + optional: true + + '@biomejs/cli-win32-x64@1.9.4': + optional: true + + '@emnapi/core@1.4.3': dependencies: - "@nodelib/fs.stat": 2.0.5 - run-parallel: 1.2.0 + '@emnapi/wasi-threads': 1.0.2 + tslib: 2.8.1 + optional: true - "@nodelib/fs.stat@2.0.5": {} - - "@nodelib/fs.walk@1.2.8": + '@emnapi/runtime@1.4.3': dependencies: - "@nodelib/fs.scandir": 2.1.5 - fastq: 1.19.1 + tslib: 2.8.1 + optional: true - "@oxc-project/types@0.70.0": {} + '@emnapi/wasi-threads@1.0.2': + dependencies: + tslib: 2.8.1 + optional: true - "@pkgr/core@0.2.7": {} + '@esbuild/aix-ppc64@0.25.5': + optional: true - "@quansync/fs@0.1.3": + '@esbuild/android-arm64@0.25.5': + optional: true + + '@esbuild/android-arm@0.25.5': + optional: true + + '@esbuild/android-x64@0.25.5': + optional: true + + '@esbuild/darwin-arm64@0.25.5': + optional: true + + '@esbuild/darwin-x64@0.25.5': + optional: true + + '@esbuild/freebsd-arm64@0.25.5': + optional: true + + '@esbuild/freebsd-x64@0.25.5': + optional: true + + '@esbuild/linux-arm64@0.25.5': + optional: true + + '@esbuild/linux-arm@0.25.5': + optional: true + + '@esbuild/linux-ia32@0.25.5': + optional: true + + '@esbuild/linux-loong64@0.25.5': + optional: true + + '@esbuild/linux-mips64el@0.25.5': + optional: true + + '@esbuild/linux-ppc64@0.25.5': + optional: true + + '@esbuild/linux-riscv64@0.25.5': + optional: true + + '@esbuild/linux-s390x@0.25.5': + optional: true + + '@esbuild/linux-x64@0.25.5': + optional: true + + '@esbuild/netbsd-arm64@0.25.5': + optional: true + + '@esbuild/netbsd-x64@0.25.5': + optional: true + + '@esbuild/openbsd-arm64@0.25.5': + optional: true + + '@esbuild/openbsd-x64@0.25.5': + optional: true + + '@esbuild/sunos-x64@0.25.5': + optional: true + + '@esbuild/win32-arm64@0.25.5': + optional: true + + '@esbuild/win32-ia32@0.25.5': + optional: true + + '@esbuild/win32-x64@0.25.5': + optional: true + + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@napi-rs/wasm-runtime@0.2.11': + dependencies: + '@emnapi/core': 1.4.3 + '@emnapi/runtime': 1.4.3 + '@tybys/wasm-util': 0.9.0 + optional: true + + '@oxc-project/types@0.70.0': {} + + '@quansync/fs@0.1.3': dependencies: quansync: 0.2.10 - "@rolldown/binding-darwin-arm64@1.0.0-beta.9": + '@rolldown/binding-darwin-arm64@1.0.0-beta.9': optional: true - "@rolldown/binding-darwin-x64@1.0.0-beta.9": + '@rolldown/binding-darwin-x64@1.0.0-beta.9': optional: true - "@rolldown/binding-freebsd-x64@1.0.0-beta.9": + '@rolldown/binding-freebsd-x64@1.0.0-beta.9': optional: true - "@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9": + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9': optional: true - "@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9": + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9': optional: true - "@rolldown/binding-linux-arm64-musl@1.0.0-beta.9": + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.9': optional: true - "@rolldown/binding-linux-x64-gnu@1.0.0-beta.9": + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.9': optional: true - "@rolldown/binding-linux-x64-musl@1.0.0-beta.9": + '@rolldown/binding-linux-x64-musl@1.0.0-beta.9': optional: true - "@rolldown/binding-wasm32-wasi@1.0.0-beta.9": + '@rolldown/binding-wasm32-wasi@1.0.0-beta.9': dependencies: - "@napi-rs/wasm-runtime": 0.2.11 + '@napi-rs/wasm-runtime': 0.2.11 optional: true - "@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9": + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9': optional: true - "@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9": + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9': optional: true - "@rolldown/binding-win32-x64-msvc@1.0.0-beta.9": + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.9': optional: true - "@rolldown/pluginutils@1.0.0-beta.9": {} + '@rolldown/pluginutils@1.0.0-beta.9': {} - "@rollup/rollup-android-arm-eabi@4.44.0": + '@rollup/rollup-android-arm-eabi@4.44.0': optional: true - "@rollup/rollup-android-arm64@4.44.0": + '@rollup/rollup-android-arm64@4.44.0': optional: true - "@rollup/rollup-darwin-arm64@4.44.0": + '@rollup/rollup-darwin-arm64@4.44.0': optional: true - "@rollup/rollup-darwin-x64@4.44.0": + '@rollup/rollup-darwin-x64@4.44.0': optional: true - "@rollup/rollup-freebsd-arm64@4.44.0": + '@rollup/rollup-freebsd-arm64@4.44.0': optional: true - "@rollup/rollup-freebsd-x64@4.44.0": + '@rollup/rollup-freebsd-x64@4.44.0': optional: true - "@rollup/rollup-linux-arm-gnueabihf@4.44.0": + '@rollup/rollup-linux-arm-gnueabihf@4.44.0': optional: true - "@rollup/rollup-linux-arm-musleabihf@4.44.0": + '@rollup/rollup-linux-arm-musleabihf@4.44.0': optional: true - "@rollup/rollup-linux-arm64-gnu@4.44.0": + '@rollup/rollup-linux-arm64-gnu@4.44.0': optional: true - "@rollup/rollup-linux-arm64-musl@4.44.0": + '@rollup/rollup-linux-arm64-musl@4.44.0': optional: true - "@rollup/rollup-linux-loongarch64-gnu@4.44.0": + '@rollup/rollup-linux-loongarch64-gnu@4.44.0': optional: true - "@rollup/rollup-linux-powerpc64le-gnu@4.44.0": + '@rollup/rollup-linux-powerpc64le-gnu@4.44.0': optional: true - "@rollup/rollup-linux-riscv64-gnu@4.44.0": + '@rollup/rollup-linux-riscv64-gnu@4.44.0': optional: true - "@rollup/rollup-linux-riscv64-musl@4.44.0": + '@rollup/rollup-linux-riscv64-musl@4.44.0': optional: true - "@rollup/rollup-linux-s390x-gnu@4.44.0": + '@rollup/rollup-linux-s390x-gnu@4.44.0': optional: true - "@rollup/rollup-linux-x64-gnu@4.44.0": + '@rollup/rollup-linux-x64-gnu@4.44.0': optional: true - "@rollup/rollup-linux-x64-musl@4.44.0": + '@rollup/rollup-linux-x64-musl@4.44.0': optional: true - "@rollup/rollup-win32-arm64-msvc@4.44.0": + '@rollup/rollup-win32-arm64-msvc@4.44.0': optional: true - "@rollup/rollup-win32-ia32-msvc@4.44.0": + '@rollup/rollup-win32-ia32-msvc@4.44.0': optional: true - "@rollup/rollup-win32-x64-msvc@4.44.0": + '@rollup/rollup-win32-x64-msvc@4.44.0': optional: true - "@sxzz/eslint-config@7.0.4(@typescript-eslint/eslint-plugin@8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(@typescript-eslint/utils@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)": - dependencies: - "@eslint-community/eslint-plugin-eslint-comments": 4.5.0(eslint@9.29.0(jiti@2.4.2)) - "@eslint/js": 9.29.0 - "@eslint/markdown": 6.6.0 - eslint: 9.29.0(jiti@2.4.2) - eslint-config-flat-gitignore: 2.1.0(eslint@9.29.0(jiti@2.4.2)) - eslint-config-prettier: 10.1.5(eslint@9.29.0(jiti@2.4.2)) - eslint-flat-config-utils: 2.1.0 - eslint-plugin-antfu: 3.1.1(eslint@9.29.0(jiti@2.4.2)) - eslint-plugin-command: 3.3.1(eslint@9.29.0(jiti@2.4.2)) - eslint-plugin-de-morgan: 1.3.0(eslint@9.29.0(jiti@2.4.2)) - eslint-plugin-import-x: 4.15.2(@typescript-eslint/utils@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2)) - eslint-plugin-jsdoc: 51.2.1(eslint@9.29.0(jiti@2.4.2)) - eslint-plugin-jsonc: 2.20.1(eslint@9.29.0(jiti@2.4.2)) - eslint-plugin-n: 17.20.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) - eslint-plugin-perfectionist: 4.15.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) - eslint-plugin-pnpm: 0.3.1(eslint@9.29.0(jiti@2.4.2)) - eslint-plugin-prettier: 5.5.0(eslint-config-prettier@10.1.5(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2))(prettier@3.6.0) - eslint-plugin-regexp: 2.9.0(eslint@9.29.0(jiti@2.4.2)) - eslint-plugin-sxzz: 0.3.0(eslint@9.29.0(jiti@2.4.2)) - eslint-plugin-unicorn: 59.0.1(eslint@9.29.0(jiti@2.4.2)) - eslint-plugin-unused-imports: 4.1.4(@typescript-eslint/eslint-plugin@8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2)) - eslint-plugin-vue: 10.2.0(eslint@9.29.0(jiti@2.4.2))(vue-eslint-parser@10.1.3(eslint@9.29.0(jiti@2.4.2))) - eslint-plugin-yml: 1.18.0(eslint@9.29.0(jiti@2.4.2)) - globals: 16.2.0 - jsonc-eslint-parser: 2.4.0 - local-pkg: 1.1.1 - prettier: 3.6.0 - typescript-eslint: 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) - vue-eslint-parser: 10.1.3(eslint@9.29.0(jiti@2.4.2)) - yaml-eslint-parser: 1.3.0 - transitivePeerDependencies: - - "@eslint/json" - - "@types/eslint" - - "@typescript-eslint/eslint-plugin" - - "@typescript-eslint/utils" - - eslint-import-resolver-node - - supports-color - - typescript - - "@sxzz/prettier-config@2.2.1": {} - - "@tybys/wasm-util@0.9.0": + '@tybys/wasm-util@0.9.0': dependencies: tslib: 2.8.1 optional: true - "@types/chai@5.2.2": + '@types/chai@5.2.2': dependencies: - "@types/deep-eql": 4.0.2 + '@types/deep-eql': 4.0.2 - "@types/debug@4.1.12": + '@types/debug@4.1.12': dependencies: - "@types/ms": 2.1.0 + '@types/ms': 2.1.0 + optional: true - "@types/deep-eql@4.0.2": {} + '@types/deep-eql@4.0.2': {} - "@types/estree@1.0.8": {} + '@types/estree@1.0.8': {} - "@types/json-schema@7.0.15": {} + '@types/ms@2.1.0': + optional: true - "@types/mdast@4.0.4": - dependencies: - "@types/unist": 3.0.3 - - "@types/ms@2.1.0": {} - - "@types/node@22.15.32": + '@types/node@22.15.32': dependencies: undici-types: 6.21.0 - "@types/unist@3.0.3": {} + '@types/uuid@10.0.0': {} - "@typescript-eslint/eslint-plugin@8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)": + '@types/ws@8.18.1': dependencies: - "@eslint-community/regexpp": 4.12.1 - "@typescript-eslint/parser": 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) - "@typescript-eslint/scope-manager": 8.34.1 - "@typescript-eslint/type-utils": 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) - "@typescript-eslint/utils": 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) - "@typescript-eslint/visitor-keys": 8.34.1 - eslint: 9.29.0(jiti@2.4.2) - graphemer: 1.4.0 - ignore: 7.0.5 - natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.8.3) - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color + '@types/node': 22.15.32 - "@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)": + '@vitest/expect@3.2.4': dependencies: - "@typescript-eslint/scope-manager": 8.34.1 - "@typescript-eslint/types": 8.34.1 - "@typescript-eslint/typescript-estree": 8.34.1(typescript@5.8.3) - "@typescript-eslint/visitor-keys": 8.34.1 - debug: 4.4.1 - eslint: 9.29.0(jiti@2.4.2) - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - - "@typescript-eslint/project-service@8.34.1(typescript@5.8.3)": - dependencies: - "@typescript-eslint/tsconfig-utils": 8.34.1(typescript@5.8.3) - "@typescript-eslint/types": 8.34.1 - debug: 4.4.1 - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - - "@typescript-eslint/scope-manager@8.34.1": - dependencies: - "@typescript-eslint/types": 8.34.1 - "@typescript-eslint/visitor-keys": 8.34.1 - - "@typescript-eslint/tsconfig-utils@8.34.1(typescript@5.8.3)": - dependencies: - typescript: 5.8.3 - - "@typescript-eslint/type-utils@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)": - dependencies: - "@typescript-eslint/typescript-estree": 8.34.1(typescript@5.8.3) - "@typescript-eslint/utils": 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) - debug: 4.4.1 - eslint: 9.29.0(jiti@2.4.2) - ts-api-utils: 2.1.0(typescript@5.8.3) - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - - "@typescript-eslint/types@8.34.1": {} - - "@typescript-eslint/typescript-estree@8.34.1(typescript@5.8.3)": - dependencies: - "@typescript-eslint/project-service": 8.34.1(typescript@5.8.3) - "@typescript-eslint/tsconfig-utils": 8.34.1(typescript@5.8.3) - "@typescript-eslint/types": 8.34.1 - "@typescript-eslint/visitor-keys": 8.34.1 - debug: 4.4.1 - fast-glob: 3.3.3 - is-glob: 4.0.3 - minimatch: 9.0.5 - semver: 7.7.2 - ts-api-utils: 2.1.0(typescript@5.8.3) - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - - "@typescript-eslint/utils@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3)": - dependencies: - "@eslint-community/eslint-utils": 4.7.0(eslint@9.29.0(jiti@2.4.2)) - "@typescript-eslint/scope-manager": 8.34.1 - "@typescript-eslint/types": 8.34.1 - "@typescript-eslint/typescript-estree": 8.34.1(typescript@5.8.3) - eslint: 9.29.0(jiti@2.4.2) - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - - "@typescript-eslint/visitor-keys@8.34.1": - dependencies: - "@typescript-eslint/types": 8.34.1 - eslint-visitor-keys: 4.2.1 - - "@unrs/resolver-binding-android-arm-eabi@1.9.1": - optional: true - - "@unrs/resolver-binding-android-arm64@1.9.1": - optional: true - - "@unrs/resolver-binding-darwin-arm64@1.9.1": - optional: true - - "@unrs/resolver-binding-darwin-x64@1.9.1": - optional: true - - "@unrs/resolver-binding-freebsd-x64@1.9.1": - optional: true - - "@unrs/resolver-binding-linux-arm-gnueabihf@1.9.1": - optional: true - - "@unrs/resolver-binding-linux-arm-musleabihf@1.9.1": - optional: true - - "@unrs/resolver-binding-linux-arm64-gnu@1.9.1": - optional: true - - "@unrs/resolver-binding-linux-arm64-musl@1.9.1": - optional: true - - "@unrs/resolver-binding-linux-ppc64-gnu@1.9.1": - optional: true - - "@unrs/resolver-binding-linux-riscv64-gnu@1.9.1": - optional: true - - "@unrs/resolver-binding-linux-riscv64-musl@1.9.1": - optional: true - - "@unrs/resolver-binding-linux-s390x-gnu@1.9.1": - optional: true - - "@unrs/resolver-binding-linux-x64-gnu@1.9.1": - optional: true - - "@unrs/resolver-binding-linux-x64-musl@1.9.1": - optional: true - - "@unrs/resolver-binding-wasm32-wasi@1.9.1": - dependencies: - "@napi-rs/wasm-runtime": 0.2.11 - optional: true - - "@unrs/resolver-binding-win32-arm64-msvc@1.9.1": - optional: true - - "@unrs/resolver-binding-win32-ia32-msvc@1.9.1": - optional: true - - "@unrs/resolver-binding-win32-x64-msvc@1.9.1": - optional: true - - "@vitest/expect@3.2.4": - dependencies: - "@types/chai": 5.2.2 - "@vitest/spy": 3.2.4 - "@vitest/utils": 3.2.4 + '@types/chai': 5.2.2 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 chai: 5.2.0 tinyrainbow: 2.0.0 - "@vitest/mocker@3.2.4(vite@6.3.5(@types/node@22.15.32)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0))": + '@vitest/mocker@3.2.4(vite@6.3.5(@types/node@22.15.32)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0))': dependencies: - "@vitest/spy": 3.2.4 + '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: vite: 6.3.5(@types/node@22.15.32)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) - "@vitest/pretty-format@3.2.4": + '@vitest/pretty-format@3.2.4': dependencies: tinyrainbow: 2.0.0 - "@vitest/runner@3.2.4": + '@vitest/runner@3.2.4': dependencies: - "@vitest/utils": 3.2.4 + '@vitest/utils': 3.2.4 pathe: 2.0.3 strip-literal: 3.0.0 - "@vitest/snapshot@3.2.4": + '@vitest/snapshot@3.2.4': dependencies: - "@vitest/pretty-format": 3.2.4 + '@vitest/pretty-format': 3.2.4 magic-string: 0.30.17 pathe: 2.0.3 - "@vitest/spy@3.2.4": + '@vitest/spy@3.2.4': dependencies: tinyspy: 4.0.3 - "@vitest/utils@3.2.4": + '@vitest/utils@3.2.4': dependencies: - "@vitest/pretty-format": 3.2.4 + '@vitest/pretty-format': 3.2.4 loupe: 3.1.4 tinyrainbow: 2.0.0 - acorn-jsx@5.3.2(acorn@8.15.0): - dependencies: - acorn: 8.15.0 - - acorn@8.15.0: {} - - ajv@6.12.6: - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - ansis@4.1.0: {} - are-docs-informative@0.0.2: {} - - argparse@2.0.1: {} - args-tokenizer@0.3.0: {} assertion-error@2.0.1: {} ast-kit@2.1.0: dependencies: - "@babel/parser": 7.27.5 + '@babel/parser': 7.27.5 pathe: 2.0.3 - balanced-match@1.0.2: {} + atomic-sleep@1.0.0: {} birpc@2.4.0: {} - boolbase@1.0.0: {} - - brace-expansion@1.1.12: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - brace-expansion@2.0.2: - dependencies: - balanced-match: 1.0.2 - - braces@3.0.3: - dependencies: - fill-range: 7.1.1 - - browserslist@4.25.0: - dependencies: - caniuse-lite: 1.0.30001724 - electron-to-chromium: 1.5.171 - node-releases: 2.0.19 - update-browserslist-db: 1.1.3(browserslist@4.25.0) - - builtin-modules@5.0.0: {} - bumpp@10.2.0: dependencies: ansis: 4.1.0 @@ -4233,12 +1416,6 @@ snapshots: cac@6.7.14: {} - callsites@3.1.0: {} - - caniuse-lite@1.0.30001724: {} - - ccount@2.0.1: {} - chai@5.2.0: dependencies: assertion-error: 2.0.1 @@ -4247,480 +1424,84 @@ snapshots: loupe: 3.1.4 pathval: 2.0.0 - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - character-entities@2.0.2: {} - check-error@2.1.1: {} chokidar@4.0.3: dependencies: readdirp: 4.1.2 - ci-info@4.2.0: {} - citty@0.1.6: dependencies: consola: 3.4.2 - clean-regexp@1.0.0: - dependencies: - escape-string-regexp: 1.0.5 - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - comment-parser@1.4.1: {} - - concat-map@0.0.1: {} - - confbox@0.1.8: {} - confbox@0.2.2: {} consola@3.4.2: {} - core-js-compat@3.43.0: - dependencies: - browserslist: 4.25.0 - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - cssesc@3.0.0: {} - debug@4.4.1: dependencies: ms: 2.1.3 - decode-named-character-reference@1.2.0: - dependencies: - character-entities: 2.0.2 - deep-eql@5.0.2: {} - deep-is@0.1.4: {} - defu@6.1.4: {} - dequal@2.0.3: {} - destr@2.0.5: {} - devlop@1.1.0: - dependencies: - dequal: 2.0.3 - diff@8.0.2: {} dotenv@16.5.0: {} dts-resolver@2.1.1: {} - electron-to-chromium@1.5.171: {} - empathic@1.1.0: {} - enhanced-resolve@5.18.1: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.2.2 - es-module-lexer@1.7.0: {} esbuild@0.25.5: optionalDependencies: - "@esbuild/aix-ppc64": 0.25.5 - "@esbuild/android-arm": 0.25.5 - "@esbuild/android-arm64": 0.25.5 - "@esbuild/android-x64": 0.25.5 - "@esbuild/darwin-arm64": 0.25.5 - "@esbuild/darwin-x64": 0.25.5 - "@esbuild/freebsd-arm64": 0.25.5 - "@esbuild/freebsd-x64": 0.25.5 - "@esbuild/linux-arm": 0.25.5 - "@esbuild/linux-arm64": 0.25.5 - "@esbuild/linux-ia32": 0.25.5 - "@esbuild/linux-loong64": 0.25.5 - "@esbuild/linux-mips64el": 0.25.5 - "@esbuild/linux-ppc64": 0.25.5 - "@esbuild/linux-riscv64": 0.25.5 - "@esbuild/linux-s390x": 0.25.5 - "@esbuild/linux-x64": 0.25.5 - "@esbuild/netbsd-arm64": 0.25.5 - "@esbuild/netbsd-x64": 0.25.5 - "@esbuild/openbsd-arm64": 0.25.5 - "@esbuild/openbsd-x64": 0.25.5 - "@esbuild/sunos-x64": 0.25.5 - "@esbuild/win32-arm64": 0.25.5 - "@esbuild/win32-ia32": 0.25.5 - "@esbuild/win32-x64": 0.25.5 + '@esbuild/aix-ppc64': 0.25.5 + '@esbuild/android-arm': 0.25.5 + '@esbuild/android-arm64': 0.25.5 + '@esbuild/android-x64': 0.25.5 + '@esbuild/darwin-arm64': 0.25.5 + '@esbuild/darwin-x64': 0.25.5 + '@esbuild/freebsd-arm64': 0.25.5 + '@esbuild/freebsd-x64': 0.25.5 + '@esbuild/linux-arm': 0.25.5 + '@esbuild/linux-arm64': 0.25.5 + '@esbuild/linux-ia32': 0.25.5 + '@esbuild/linux-loong64': 0.25.5 + '@esbuild/linux-mips64el': 0.25.5 + '@esbuild/linux-ppc64': 0.25.5 + '@esbuild/linux-riscv64': 0.25.5 + '@esbuild/linux-s390x': 0.25.5 + '@esbuild/linux-x64': 0.25.5 + '@esbuild/netbsd-arm64': 0.25.5 + '@esbuild/netbsd-x64': 0.25.5 + '@esbuild/openbsd-arm64': 0.25.5 + '@esbuild/openbsd-x64': 0.25.5 + '@esbuild/sunos-x64': 0.25.5 + '@esbuild/win32-arm64': 0.25.5 + '@esbuild/win32-ia32': 0.25.5 + '@esbuild/win32-x64': 0.25.5 escalade@3.2.0: {} - escape-string-regexp@1.0.5: {} - - escape-string-regexp@4.0.0: {} - - escape-string-regexp@5.0.0: {} - - eslint-compat-utils@0.5.1(eslint@9.29.0(jiti@2.4.2)): - dependencies: - eslint: 9.29.0(jiti@2.4.2) - semver: 7.7.2 - - eslint-compat-utils@0.6.5(eslint@9.29.0(jiti@2.4.2)): - dependencies: - eslint: 9.29.0(jiti@2.4.2) - semver: 7.7.2 - - eslint-config-flat-gitignore@2.1.0(eslint@9.29.0(jiti@2.4.2)): - dependencies: - "@eslint/compat": 1.3.0(eslint@9.29.0(jiti@2.4.2)) - eslint: 9.29.0(jiti@2.4.2) - - eslint-config-prettier@10.1.5(eslint@9.29.0(jiti@2.4.2)): - dependencies: - eslint: 9.29.0(jiti@2.4.2) - - eslint-flat-config-utils@2.1.0: - dependencies: - pathe: 2.0.3 - - eslint-import-context@0.1.8(unrs-resolver@1.9.1): - dependencies: - get-tsconfig: 4.10.1 - stable-hash-x: 0.1.1 - optionalDependencies: - unrs-resolver: 1.9.1 - - eslint-json-compat-utils@0.2.1(eslint@9.29.0(jiti@2.4.2))(jsonc-eslint-parser@2.4.0): - dependencies: - eslint: 9.29.0(jiti@2.4.2) - esquery: 1.6.0 - jsonc-eslint-parser: 2.4.0 - - eslint-plugin-antfu@3.1.1(eslint@9.29.0(jiti@2.4.2)): - dependencies: - eslint: 9.29.0(jiti@2.4.2) - - eslint-plugin-command@3.3.1(eslint@9.29.0(jiti@2.4.2)): - dependencies: - "@es-joy/jsdoccomment": 0.50.2 - eslint: 9.29.0(jiti@2.4.2) - - eslint-plugin-de-morgan@1.3.0(eslint@9.29.0(jiti@2.4.2)): - dependencies: - eslint: 9.29.0(jiti@2.4.2) - - eslint-plugin-es-x@7.8.0(eslint@9.29.0(jiti@2.4.2)): - dependencies: - "@eslint-community/eslint-utils": 4.7.0(eslint@9.29.0(jiti@2.4.2)) - "@eslint-community/regexpp": 4.12.1 - eslint: 9.29.0(jiti@2.4.2) - eslint-compat-utils: 0.5.1(eslint@9.29.0(jiti@2.4.2)) - - eslint-plugin-import-x@4.15.2(@typescript-eslint/utils@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2)): - dependencies: - "@typescript-eslint/types": 8.34.1 - comment-parser: 1.4.1 - debug: 4.4.1 - eslint: 9.29.0(jiti@2.4.2) - eslint-import-context: 0.1.8(unrs-resolver@1.9.1) - is-glob: 4.0.3 - minimatch: 10.0.3 - semver: 7.7.2 - stable-hash-x: 0.1.1 - unrs-resolver: 1.9.1 - optionalDependencies: - "@typescript-eslint/utils": 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) - transitivePeerDependencies: - - supports-color - - eslint-plugin-jsdoc@51.2.1(eslint@9.29.0(jiti@2.4.2)): - dependencies: - "@es-joy/jsdoccomment": 0.52.0 - are-docs-informative: 0.0.2 - comment-parser: 1.4.1 - debug: 4.4.1 - escape-string-regexp: 4.0.0 - eslint: 9.29.0(jiti@2.4.2) - espree: 10.4.0 - esquery: 1.6.0 - parse-imports-exports: 0.2.4 - semver: 7.7.2 - spdx-expression-parse: 4.0.0 - transitivePeerDependencies: - - supports-color - - eslint-plugin-jsonc@2.20.1(eslint@9.29.0(jiti@2.4.2)): - dependencies: - "@eslint-community/eslint-utils": 4.7.0(eslint@9.29.0(jiti@2.4.2)) - eslint: 9.29.0(jiti@2.4.2) - eslint-compat-utils: 0.6.5(eslint@9.29.0(jiti@2.4.2)) - eslint-json-compat-utils: 0.2.1(eslint@9.29.0(jiti@2.4.2))(jsonc-eslint-parser@2.4.0) - espree: 10.4.0 - graphemer: 1.4.0 - jsonc-eslint-parser: 2.4.0 - natural-compare: 1.4.0 - synckit: 0.11.8 - transitivePeerDependencies: - - "@eslint/json" - - eslint-plugin-n@17.20.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3): - dependencies: - "@eslint-community/eslint-utils": 4.7.0(eslint@9.29.0(jiti@2.4.2)) - "@typescript-eslint/utils": 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) - enhanced-resolve: 5.18.1 - eslint: 9.29.0(jiti@2.4.2) - eslint-plugin-es-x: 7.8.0(eslint@9.29.0(jiti@2.4.2)) - get-tsconfig: 4.10.1 - globals: 15.15.0 - ignore: 5.3.2 - minimatch: 9.0.5 - semver: 7.7.2 - ts-declaration-location: 1.0.7(typescript@5.8.3) - transitivePeerDependencies: - - supports-color - - typescript - - eslint-plugin-perfectionist@4.15.0(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3): - dependencies: - "@typescript-eslint/types": 8.34.1 - "@typescript-eslint/utils": 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) - eslint: 9.29.0(jiti@2.4.2) - natural-orderby: 5.0.0 - transitivePeerDependencies: - - supports-color - - typescript - - eslint-plugin-pnpm@0.3.1(eslint@9.29.0(jiti@2.4.2)): - dependencies: - eslint: 9.29.0(jiti@2.4.2) - find-up-simple: 1.0.1 - jsonc-eslint-parser: 2.4.0 - pathe: 2.0.3 - pnpm-workspace-yaml: 0.3.1 - tinyglobby: 0.2.14 - yaml-eslint-parser: 1.3.0 - - eslint-plugin-prettier@5.5.0(eslint-config-prettier@10.1.5(eslint@9.29.0(jiti@2.4.2)))(eslint@9.29.0(jiti@2.4.2))(prettier@3.6.0): - dependencies: - eslint: 9.29.0(jiti@2.4.2) - prettier: 3.6.0 - prettier-linter-helpers: 1.0.0 - synckit: 0.11.8 - optionalDependencies: - eslint-config-prettier: 10.1.5(eslint@9.29.0(jiti@2.4.2)) - - eslint-plugin-regexp@2.9.0(eslint@9.29.0(jiti@2.4.2)): - dependencies: - "@eslint-community/eslint-utils": 4.7.0(eslint@9.29.0(jiti@2.4.2)) - "@eslint-community/regexpp": 4.12.1 - comment-parser: 1.4.1 - eslint: 9.29.0(jiti@2.4.2) - jsdoc-type-pratt-parser: 4.1.0 - refa: 0.12.1 - regexp-ast-analysis: 0.7.1 - scslre: 0.3.0 - - eslint-plugin-sxzz@0.3.0(eslint@9.29.0(jiti@2.4.2)): - dependencies: - eslint: 9.29.0(jiti@2.4.2) - - eslint-plugin-unicorn@59.0.1(eslint@9.29.0(jiti@2.4.2)): - dependencies: - "@babel/helper-validator-identifier": 7.27.1 - "@eslint-community/eslint-utils": 4.7.0(eslint@9.29.0(jiti@2.4.2)) - "@eslint/plugin-kit": 0.2.8 - ci-info: 4.2.0 - clean-regexp: 1.0.0 - core-js-compat: 3.43.0 - eslint: 9.29.0(jiti@2.4.2) - esquery: 1.6.0 - find-up-simple: 1.0.1 - globals: 16.2.0 - indent-string: 5.0.0 - is-builtin-module: 5.0.0 - jsesc: 3.1.0 - pluralize: 8.0.0 - regexp-tree: 0.1.27 - regjsparser: 0.12.0 - semver: 7.7.2 - strip-indent: 4.0.0 - - eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2)): - dependencies: - eslint: 9.29.0(jiti@2.4.2) - optionalDependencies: - "@typescript-eslint/eslint-plugin": 8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) - - eslint-plugin-vue@10.2.0(eslint@9.29.0(jiti@2.4.2))(vue-eslint-parser@10.1.3(eslint@9.29.0(jiti@2.4.2))): - dependencies: - "@eslint-community/eslint-utils": 4.7.0(eslint@9.29.0(jiti@2.4.2)) - eslint: 9.29.0(jiti@2.4.2) - natural-compare: 1.4.0 - nth-check: 2.1.1 - postcss-selector-parser: 6.1.2 - semver: 7.7.2 - vue-eslint-parser: 10.1.3(eslint@9.29.0(jiti@2.4.2)) - xml-name-validator: 4.0.0 - - eslint-plugin-yml@1.18.0(eslint@9.29.0(jiti@2.4.2)): - dependencies: - debug: 4.4.1 - escape-string-regexp: 4.0.0 - eslint: 9.29.0(jiti@2.4.2) - eslint-compat-utils: 0.6.5(eslint@9.29.0(jiti@2.4.2)) - natural-compare: 1.4.0 - yaml-eslint-parser: 1.3.0 - transitivePeerDependencies: - - supports-color - - eslint-scope@8.4.0: - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - - eslint-visitor-keys@3.4.3: {} - - eslint-visitor-keys@4.2.1: {} - - eslint@9.29.0(jiti@2.4.2): - dependencies: - "@eslint-community/eslint-utils": 4.7.0(eslint@9.29.0(jiti@2.4.2)) - "@eslint-community/regexpp": 4.12.1 - "@eslint/config-array": 0.20.1 - "@eslint/config-helpers": 0.2.3 - "@eslint/core": 0.14.0 - "@eslint/eslintrc": 3.3.1 - "@eslint/js": 9.29.0 - "@eslint/plugin-kit": 0.3.2 - "@humanfs/node": 0.16.6 - "@humanwhocodes/module-importer": 1.0.1 - "@humanwhocodes/retry": 0.4.3 - "@types/estree": 1.0.8 - "@types/json-schema": 7.0.15 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.1 - escape-string-regexp: 4.0.0 - eslint-scope: 8.4.0 - eslint-visitor-keys: 4.2.1 - espree: 10.4.0 - esquery: 1.6.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 8.0.0 - find-up: 5.0.0 - glob-parent: 6.0.2 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - json-stable-stringify-without-jsonify: 1.0.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.4 - optionalDependencies: - jiti: 2.4.2 - transitivePeerDependencies: - - supports-color - - espree@10.4.0: - dependencies: - acorn: 8.15.0 - acorn-jsx: 5.3.2(acorn@8.15.0) - eslint-visitor-keys: 4.2.1 - - espree@9.6.1: - dependencies: - acorn: 8.15.0 - acorn-jsx: 5.3.2(acorn@8.15.0) - eslint-visitor-keys: 3.4.3 - - esquery@1.6.0: - dependencies: - estraverse: 5.3.0 - - esrecurse@4.3.0: - dependencies: - estraverse: 5.3.0 - - estraverse@5.3.0: {} - estree-walker@3.0.3: dependencies: - "@types/estree": 1.0.8 - - esutils@2.0.3: {} + '@types/estree': 1.0.8 expect-type@1.2.1: {} exsolve@1.0.7: {} - fast-deep-equal@3.1.3: {} - - fast-diff@1.3.0: {} - - fast-glob@3.3.3: - dependencies: - "@nodelib/fs.stat": 2.0.5 - "@nodelib/fs.walk": 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - - fast-json-stable-stringify@2.1.0: {} - - fast-levenshtein@2.0.6: {} - - fastq@1.19.1: - dependencies: - reusify: 1.1.0 - - fault@2.0.1: - dependencies: - format: 0.2.2 + fast-redact@3.5.0: {} fdir@6.4.6(picomatch@4.0.2): optionalDependencies: picomatch: 4.0.2 - file-entry-cache@8.0.0: - dependencies: - flat-cache: 4.0.1 - - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - - find-up-simple@1.0.1: {} - - find-up@5.0.0: - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - - flat-cache@4.0.1: - dependencies: - flatted: 3.3.3 - keyv: 4.5.4 - - flatted@3.3.3: {} - - format@0.2.2: {} - fsevents@2.3.3: optional: true @@ -4737,481 +1518,33 @@ snapshots: nypm: 0.6.0 pathe: 2.0.3 - github-slugger@2.0.0: {} - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - - glob-parent@6.0.2: - dependencies: - is-glob: 4.0.3 - - globals@14.0.0: {} - - globals@15.15.0: {} - - globals@16.2.0: {} - - graceful-fs@4.2.11: {} - - graphemer@1.4.0: {} - happy-dom@17.6.3: dependencies: webidl-conversions: 7.0.0 whatwg-mimetype: 3.0.0 - has-flag@4.0.0: {} - hookable@5.5.3: {} - ignore@5.3.2: {} - - ignore@7.0.5: {} - - import-fresh@3.3.1: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - - imurmurhash@0.1.4: {} - - indent-string@5.0.0: {} - - is-builtin-module@5.0.0: - dependencies: - builtin-modules: 5.0.0 - - is-extglob@2.1.1: {} - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - - is-number@7.0.0: {} - - isexe@2.0.0: {} - jiti@2.4.2: {} js-tokens@9.0.1: {} - js-yaml@4.1.0: - dependencies: - argparse: 2.0.1 - - jsdoc-type-pratt-parser@4.1.0: {} - - jsesc@3.0.2: {} - jsesc@3.1.0: {} - json-buffer@3.0.1: {} - - json-schema-traverse@0.4.1: {} - - json-stable-stringify-without-jsonify@1.0.1: {} - - jsonc-eslint-parser@2.4.0: - dependencies: - acorn: 8.15.0 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - semver: 7.7.2 - jsonc-parser@3.3.1: {} - keyv@4.5.4: - dependencies: - json-buffer: 3.0.1 - - levn@0.4.1: - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - - local-pkg@1.1.1: - dependencies: - mlly: 1.7.4 - pkg-types: 2.1.0 - quansync: 0.2.10 - - locate-path@6.0.0: - dependencies: - p-locate: 5.0.0 - - lodash.merge@4.6.2: {} - - lodash@4.17.21: {} - - longest-streak@3.1.0: {} - loupe@3.1.4: {} magic-string@0.30.17: dependencies: - "@jridgewell/sourcemap-codec": 1.5.0 - - markdown-table@3.0.4: {} - - mdast-util-find-and-replace@3.0.2: - dependencies: - "@types/mdast": 4.0.4 - escape-string-regexp: 5.0.0 - unist-util-is: 6.0.0 - unist-util-visit-parents: 6.0.1 - - mdast-util-from-markdown@2.0.2: - dependencies: - "@types/mdast": 4.0.4 - "@types/unist": 3.0.3 - decode-named-character-reference: 1.2.0 - devlop: 1.1.0 - mdast-util-to-string: 4.0.0 - micromark: 4.0.2 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-decode-string: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - unist-util-stringify-position: 4.0.0 - transitivePeerDependencies: - - supports-color - - mdast-util-frontmatter@2.0.1: - dependencies: - "@types/mdast": 4.0.4 - devlop: 1.1.0 - escape-string-regexp: 5.0.0 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 - micromark-extension-frontmatter: 2.0.0 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-autolink-literal@2.0.1: - dependencies: - "@types/mdast": 4.0.4 - ccount: 2.0.1 - devlop: 1.1.0 - mdast-util-find-and-replace: 3.0.2 - micromark-util-character: 2.1.1 - - mdast-util-gfm-footnote@2.1.0: - dependencies: - "@types/mdast": 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 - micromark-util-normalize-identifier: 2.0.1 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-strikethrough@2.0.0: - dependencies: - "@types/mdast": 4.0.4 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-table@2.0.0: - dependencies: - "@types/mdast": 4.0.4 - devlop: 1.1.0 - markdown-table: 3.0.4 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-task-list-item@2.0.0: - dependencies: - "@types/mdast": 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm@3.1.0: - dependencies: - mdast-util-from-markdown: 2.0.2 - mdast-util-gfm-autolink-literal: 2.0.1 - mdast-util-gfm-footnote: 2.1.0 - mdast-util-gfm-strikethrough: 2.0.0 - mdast-util-gfm-table: 2.0.0 - mdast-util-gfm-task-list-item: 2.0.0 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-phrasing@4.1.0: - dependencies: - "@types/mdast": 4.0.4 - unist-util-is: 6.0.0 - - mdast-util-to-markdown@2.1.2: - dependencies: - "@types/mdast": 4.0.4 - "@types/unist": 3.0.3 - longest-streak: 3.1.0 - mdast-util-phrasing: 4.1.0 - mdast-util-to-string: 4.0.0 - micromark-util-classify-character: 2.0.1 - micromark-util-decode-string: 2.0.1 - unist-util-visit: 5.0.0 - zwitch: 2.0.4 - - mdast-util-to-string@4.0.0: - dependencies: - "@types/mdast": 4.0.4 - - merge2@1.4.1: {} - - micromark-core-commonmark@2.0.3: - dependencies: - decode-named-character-reference: 1.2.0 - devlop: 1.1.0 - micromark-factory-destination: 2.0.1 - micromark-factory-label: 2.0.1 - micromark-factory-space: 2.0.1 - micromark-factory-title: 2.0.1 - micromark-factory-whitespace: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-chunked: 2.0.1 - micromark-util-classify-character: 2.0.1 - micromark-util-html-tag-name: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-subtokenize: 2.1.0 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-frontmatter@2.0.0: - dependencies: - fault: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-autolink-literal@2.1.0: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-sanitize-uri: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-footnote@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-core-commonmark: 2.0.3 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-sanitize-uri: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-strikethrough@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-util-chunked: 2.0.1 - micromark-util-classify-character: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-table@2.1.1: - dependencies: - devlop: 1.1.0 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-tagfilter@2.0.0: - dependencies: - micromark-util-types: 2.0.2 - - micromark-extension-gfm-task-list-item@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm@3.0.0: - dependencies: - micromark-extension-gfm-autolink-literal: 2.1.0 - micromark-extension-gfm-footnote: 2.1.0 - micromark-extension-gfm-strikethrough: 2.1.0 - micromark-extension-gfm-table: 2.1.1 - micromark-extension-gfm-tagfilter: 2.0.0 - micromark-extension-gfm-task-list-item: 2.1.0 - micromark-util-combine-extensions: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-destination@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-label@2.0.1: - dependencies: - devlop: 1.1.0 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-space@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-types: 2.0.2 - - micromark-factory-title@2.0.1: - dependencies: - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-whitespace@2.0.1: - dependencies: - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-character@2.1.1: - dependencies: - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-chunked@2.0.1: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-classify-character@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-combine-extensions@2.0.1: - dependencies: - micromark-util-chunked: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-decode-numeric-character-reference@2.0.2: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-decode-string@2.0.1: - dependencies: - decode-named-character-reference: 1.2.0 - micromark-util-character: 2.1.1 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-symbol: 2.0.1 - - micromark-util-encode@2.0.1: {} - - micromark-util-html-tag-name@2.0.1: {} - - micromark-util-normalize-identifier@2.0.1: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-resolve-all@2.0.1: - dependencies: - micromark-util-types: 2.0.2 - - micromark-util-sanitize-uri@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-encode: 2.0.1 - micromark-util-symbol: 2.0.1 - - micromark-util-subtokenize@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-util-chunked: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-symbol@2.0.1: {} - - micromark-util-types@2.0.2: {} - - micromark@4.0.2: - dependencies: - "@types/debug": 4.1.12 - debug: 4.4.1 - decode-named-character-reference: 1.2.0 - devlop: 1.1.0 - micromark-core-commonmark: 2.0.3 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-chunked: 2.0.1 - micromark-util-combine-extensions: 2.0.1 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-encode: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-sanitize-uri: 2.0.1 - micromark-util-subtokenize: 2.1.0 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - transitivePeerDependencies: - - supports-color - - micromatch@4.0.8: - dependencies: - braces: 3.0.3 - picomatch: 2.3.1 - - min-indent@1.0.1: {} - - minimatch@10.0.3: - dependencies: - "@isaacs/brace-expansion": 5.0.0 - - minimatch@3.1.2: - dependencies: - brace-expansion: 1.1.12 - - minimatch@9.0.5: - dependencies: - brace-expansion: 2.0.2 - - mlly@1.7.4: - dependencies: - acorn: 8.15.0 - pathe: 2.0.3 - pkg-types: 1.3.1 - ufo: 1.6.1 + '@jridgewell/sourcemap-codec': 1.5.0 ms@2.1.3: {} nanoid@3.3.11: {} - napi-postinstall@0.2.4: {} - - natural-compare@1.4.0: {} - - natural-orderby@5.0.0: {} - node-fetch-native@1.6.6: {} - node-releases@2.0.19: {} - - nth-check@2.1.1: - dependencies: - boolbase: 1.0.0 - nypm@0.6.0: dependencies: citty: 0.1.6 @@ -5222,39 +1555,10 @@ snapshots: ohash@2.0.11: {} - optionator@0.9.4: - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.5 - - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - - p-locate@5.0.0: - dependencies: - p-limit: 3.1.0 + on-exit-leak-free@2.1.2: {} package-manager-detector@1.3.0: {} - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 - - parse-imports-exports@0.2.4: - dependencies: - parse-statements: 1.0.11 - - parse-statements@1.0.11: {} - - path-exists@4.0.0: {} - - path-key@3.1.1: {} - pathe@2.0.3: {} pathval@2.0.0: {} @@ -5263,15 +1567,27 @@ snapshots: picocolors@1.1.1: {} - picomatch@2.3.1: {} - picomatch@4.0.2: {} - pkg-types@1.3.1: + pino-abstract-transport@2.0.0: dependencies: - confbox: 0.1.8 - mlly: 1.7.4 - pathe: 2.0.3 + split2: 4.2.0 + + pino-std-serializers@7.0.0: {} + + pino@9.7.0: + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.5.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pino-std-serializers: 7.0.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.0 + thread-stream: 3.1.0 pkg-types@2.1.0: dependencies: @@ -5279,36 +1595,19 @@ snapshots: exsolve: 1.0.7 pathe: 2.0.3 - pluralize@8.0.0: {} - - pnpm-workspace-yaml@0.3.1: - dependencies: - yaml: 2.8.0 - - postcss-selector-parser@6.1.2: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - postcss@8.5.6: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 source-map-js: 1.2.1 - prelude-ls@1.2.1: {} + posthog-node@5.1.1: {} - prettier-linter-helpers@1.0.0: - dependencies: - fast-diff: 1.3.0 - - prettier@3.6.0: {} - - punycode@2.3.1: {} + process-warning@5.0.0: {} quansync@0.2.10: {} - queue-microtask@1.2.3: {} + quick-format-unescaped@4.0.4: {} rc9@2.1.2: dependencies: @@ -5317,32 +1616,15 @@ snapshots: readdirp@4.1.2: {} - refa@0.12.1: - dependencies: - "@eslint-community/regexpp": 4.12.1 - - regexp-ast-analysis@0.7.1: - dependencies: - "@eslint-community/regexpp": 4.12.1 - refa: 0.12.1 - - regexp-tree@0.1.27: {} - - regjsparser@0.12.0: - dependencies: - jsesc: 3.0.2 - - resolve-from@4.0.0: {} + real-require@0.2.0: {} resolve-pkg-maps@1.0.0: {} - reusify@1.1.0: {} - rolldown-plugin-dts@0.13.12(rolldown@1.0.0-beta.9)(typescript@5.8.3): dependencies: - "@babel/generator": 7.27.5 - "@babel/parser": 7.27.5 - "@babel/types": 7.27.6 + '@babel/generator': 7.27.5 + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 ast-kit: 2.1.0 birpc: 2.4.0 debug: 4.4.1 @@ -5357,105 +1639,74 @@ snapshots: rolldown@1.0.0-beta.9: dependencies: - "@oxc-project/types": 0.70.0 - "@rolldown/pluginutils": 1.0.0-beta.9 + '@oxc-project/types': 0.70.0 + '@rolldown/pluginutils': 1.0.0-beta.9 ansis: 4.1.0 optionalDependencies: - "@rolldown/binding-darwin-arm64": 1.0.0-beta.9 - "@rolldown/binding-darwin-x64": 1.0.0-beta.9 - "@rolldown/binding-freebsd-x64": 1.0.0-beta.9 - "@rolldown/binding-linux-arm-gnueabihf": 1.0.0-beta.9 - "@rolldown/binding-linux-arm64-gnu": 1.0.0-beta.9 - "@rolldown/binding-linux-arm64-musl": 1.0.0-beta.9 - "@rolldown/binding-linux-x64-gnu": 1.0.0-beta.9 - "@rolldown/binding-linux-x64-musl": 1.0.0-beta.9 - "@rolldown/binding-wasm32-wasi": 1.0.0-beta.9 - "@rolldown/binding-win32-arm64-msvc": 1.0.0-beta.9 - "@rolldown/binding-win32-ia32-msvc": 1.0.0-beta.9 - "@rolldown/binding-win32-x64-msvc": 1.0.0-beta.9 + '@rolldown/binding-darwin-arm64': 1.0.0-beta.9 + '@rolldown/binding-darwin-x64': 1.0.0-beta.9 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.9 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.9 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.9 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.9 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.9 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.9 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.9 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.9 + '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.9 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.9 rollup@4.44.0: dependencies: - "@types/estree": 1.0.8 + '@types/estree': 1.0.8 optionalDependencies: - "@rollup/rollup-android-arm-eabi": 4.44.0 - "@rollup/rollup-android-arm64": 4.44.0 - "@rollup/rollup-darwin-arm64": 4.44.0 - "@rollup/rollup-darwin-x64": 4.44.0 - "@rollup/rollup-freebsd-arm64": 4.44.0 - "@rollup/rollup-freebsd-x64": 4.44.0 - "@rollup/rollup-linux-arm-gnueabihf": 4.44.0 - "@rollup/rollup-linux-arm-musleabihf": 4.44.0 - "@rollup/rollup-linux-arm64-gnu": 4.44.0 - "@rollup/rollup-linux-arm64-musl": 4.44.0 - "@rollup/rollup-linux-loongarch64-gnu": 4.44.0 - "@rollup/rollup-linux-powerpc64le-gnu": 4.44.0 - "@rollup/rollup-linux-riscv64-gnu": 4.44.0 - "@rollup/rollup-linux-riscv64-musl": 4.44.0 - "@rollup/rollup-linux-s390x-gnu": 4.44.0 - "@rollup/rollup-linux-x64-gnu": 4.44.0 - "@rollup/rollup-linux-x64-musl": 4.44.0 - "@rollup/rollup-win32-arm64-msvc": 4.44.0 - "@rollup/rollup-win32-ia32-msvc": 4.44.0 - "@rollup/rollup-win32-x64-msvc": 4.44.0 + '@rollup/rollup-android-arm-eabi': 4.44.0 + '@rollup/rollup-android-arm64': 4.44.0 + '@rollup/rollup-darwin-arm64': 4.44.0 + '@rollup/rollup-darwin-x64': 4.44.0 + '@rollup/rollup-freebsd-arm64': 4.44.0 + '@rollup/rollup-freebsd-x64': 4.44.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.44.0 + '@rollup/rollup-linux-arm-musleabihf': 4.44.0 + '@rollup/rollup-linux-arm64-gnu': 4.44.0 + '@rollup/rollup-linux-arm64-musl': 4.44.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.44.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.44.0 + '@rollup/rollup-linux-riscv64-gnu': 4.44.0 + '@rollup/rollup-linux-riscv64-musl': 4.44.0 + '@rollup/rollup-linux-s390x-gnu': 4.44.0 + '@rollup/rollup-linux-x64-gnu': 4.44.0 + '@rollup/rollup-linux-x64-musl': 4.44.0 + '@rollup/rollup-win32-arm64-msvc': 4.44.0 + '@rollup/rollup-win32-ia32-msvc': 4.44.0 + '@rollup/rollup-win32-x64-msvc': 4.44.0 fsevents: 2.3.3 - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - - scslre@0.3.0: - dependencies: - "@eslint-community/regexpp": 4.12.1 - refa: 0.12.1 - regexp-ast-analysis: 0.7.1 + safe-stable-stringify@2.5.0: {} semver@7.7.2: {} - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - siginfo@2.0.0: {} + sonic-boom@4.2.0: + dependencies: + atomic-sleep: 1.0.0 + source-map-js@1.2.1: {} - spdx-exceptions@2.5.0: {} - - spdx-expression-parse@4.0.0: - dependencies: - spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.21 - - spdx-license-ids@3.0.21: {} - - stable-hash-x@0.1.1: {} + split2@4.2.0: {} stackback@0.0.2: {} std-env@3.9.0: {} - strip-indent@4.0.0: - dependencies: - min-indent: 1.0.1 - - strip-json-comments@3.1.1: {} - strip-literal@3.0.0: dependencies: js-tokens: 9.0.1 - supports-color@7.2.0: + thread-stream@3.1.0: dependencies: - has-flag: 4.0.0 - - synckit@0.11.8: - dependencies: - "@pkgr/core": 0.2.7 - - tapable@2.2.2: {} + real-require: 0.2.0 tinybench@2.9.0: {} @@ -5474,19 +1725,6 @@ snapshots: tinyspy@4.0.3: {} - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - - ts-api-utils@2.1.0(typescript@5.8.3): - dependencies: - typescript: 5.8.3 - - ts-declaration-location@1.0.7(typescript@5.8.3): - dependencies: - picomatch: 4.0.2 - typescript: 5.8.3 - tsdown@0.11.13(typescript@5.8.3): dependencies: ansis: 4.1.0 @@ -5505,8 +1743,8 @@ snapshots: optionalDependencies: typescript: 5.8.3 transitivePeerDependencies: - - "@oxc-project/runtime" - - "@typescript/native-preview" + - '@oxc-project/runtime' + - '@typescript/native-preview' - oxc-resolver - supports-color - vue-tsc @@ -5521,87 +1759,18 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - type-check@0.4.0: - dependencies: - prelude-ls: 1.2.1 - - typescript-eslint@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3): - dependencies: - "@typescript-eslint/eslint-plugin": 8.34.1(@typescript-eslint/parser@8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) - "@typescript-eslint/parser": 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) - "@typescript-eslint/utils": 8.34.1(eslint@9.29.0(jiti@2.4.2))(typescript@5.8.3) - eslint: 9.29.0(jiti@2.4.2) - typescript: 5.8.3 - transitivePeerDependencies: - - supports-color - typescript@5.8.3: {} - ufo@1.6.1: {} - unconfig@7.3.2: dependencies: - "@quansync/fs": 0.1.3 + '@quansync/fs': 0.1.3 defu: 6.1.4 jiti: 2.4.2 quansync: 0.2.10 undici-types@6.21.0: {} - unist-util-is@6.0.0: - dependencies: - "@types/unist": 3.0.3 - - unist-util-stringify-position@4.0.0: - dependencies: - "@types/unist": 3.0.3 - - unist-util-visit-parents@6.0.1: - dependencies: - "@types/unist": 3.0.3 - unist-util-is: 6.0.0 - - unist-util-visit@5.0.0: - dependencies: - "@types/unist": 3.0.3 - unist-util-is: 6.0.0 - unist-util-visit-parents: 6.0.1 - - unrs-resolver@1.9.1: - dependencies: - napi-postinstall: 0.2.4 - optionalDependencies: - "@unrs/resolver-binding-android-arm-eabi": 1.9.1 - "@unrs/resolver-binding-android-arm64": 1.9.1 - "@unrs/resolver-binding-darwin-arm64": 1.9.1 - "@unrs/resolver-binding-darwin-x64": 1.9.1 - "@unrs/resolver-binding-freebsd-x64": 1.9.1 - "@unrs/resolver-binding-linux-arm-gnueabihf": 1.9.1 - "@unrs/resolver-binding-linux-arm-musleabihf": 1.9.1 - "@unrs/resolver-binding-linux-arm64-gnu": 1.9.1 - "@unrs/resolver-binding-linux-arm64-musl": 1.9.1 - "@unrs/resolver-binding-linux-ppc64-gnu": 1.9.1 - "@unrs/resolver-binding-linux-riscv64-gnu": 1.9.1 - "@unrs/resolver-binding-linux-riscv64-musl": 1.9.1 - "@unrs/resolver-binding-linux-s390x-gnu": 1.9.1 - "@unrs/resolver-binding-linux-x64-gnu": 1.9.1 - "@unrs/resolver-binding-linux-x64-musl": 1.9.1 - "@unrs/resolver-binding-wasm32-wasi": 1.9.1 - "@unrs/resolver-binding-win32-arm64-msvc": 1.9.1 - "@unrs/resolver-binding-win32-ia32-msvc": 1.9.1 - "@unrs/resolver-binding-win32-x64-msvc": 1.9.1 - - update-browserslist-db@1.1.3(browserslist@4.25.0): - dependencies: - browserslist: 4.25.0 - escalade: 3.2.0 - picocolors: 1.1.1 - - uri-js@4.4.1: - dependencies: - punycode: 2.3.1 - - util-deprecate@1.0.2: {} + uuid@11.1.0: {} vite-node@3.2.4(@types/node@22.15.32)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0): dependencies: @@ -5611,7 +1780,7 @@ snapshots: pathe: 2.0.3 vite: 6.3.5(@types/node@22.15.32)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) transitivePeerDependencies: - - "@types/node" + - '@types/node' - jiti - less - lightningcss @@ -5633,7 +1802,7 @@ snapshots: rollup: 4.44.0 tinyglobby: 0.2.14 optionalDependencies: - "@types/node": 22.15.32 + '@types/node': 22.15.32 fsevents: 2.3.3 jiti: 2.4.2 tsx: 4.20.3 @@ -5641,14 +1810,14 @@ snapshots: vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.32)(happy-dom@17.6.3)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0): dependencies: - "@types/chai": 5.2.2 - "@vitest/expect": 3.2.4 - "@vitest/mocker": 3.2.4(vite@6.3.5(@types/node@22.15.32)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0)) - "@vitest/pretty-format": 3.2.4 - "@vitest/runner": 3.2.4 - "@vitest/snapshot": 3.2.4 - "@vitest/spy": 3.2.4 - "@vitest/utils": 3.2.4 + '@types/chai': 5.2.2 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@22.15.32)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 chai: 5.2.0 debug: 4.4.1 expect-type: 1.2.1 @@ -5665,8 +1834,8 @@ snapshots: vite-node: 3.2.4(@types/node@22.15.32)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) why-is-node-running: 2.3.0 optionalDependencies: - "@types/debug": 4.1.12 - "@types/node": 22.15.32 + '@types/debug': 4.1.12 + '@types/node': 22.15.32 happy-dom: 17.6.3 transitivePeerDependencies: - jiti @@ -5682,43 +1851,13 @@ snapshots: - tsx - yaml - vue-eslint-parser@10.1.3(eslint@9.29.0(jiti@2.4.2)): - dependencies: - debug: 4.4.1 - eslint: 9.29.0(jiti@2.4.2) - eslint-scope: 8.4.0 - eslint-visitor-keys: 4.2.1 - espree: 10.4.0 - esquery: 1.6.0 - lodash: 4.17.21 - semver: 7.7.2 - transitivePeerDependencies: - - supports-color - webidl-conversions@7.0.0: {} whatwg-mimetype@3.0.0: {} - which@2.0.2: - dependencies: - isexe: 2.0.0 - why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 - word-wrap@1.2.5: {} - - xml-name-validator@4.0.0: {} - - yaml-eslint-parser@1.3.0: - dependencies: - eslint-visitor-keys: 3.4.3 - yaml: 2.8.0 - yaml@2.8.0: {} - - yocto-queue@0.1.0: {} - - zwitch@2.0.4: {} diff --git a/libs/typescript/core/src/index.ts b/libs/typescript/core/src/index.ts index da86d36e..d986c9d6 100644 --- a/libs/typescript/core/src/index.ts +++ b/libs/typescript/core/src/index.ts @@ -1,3 +1,7 @@ -export const myFunction = () => { - return 'Hello, world!' -} +/** + * This module provides the core telemetry functionality for CUA libraries. + * + * It provides a low-overhead way to collect anonymous usage data. + */ + +export * from './telemetry'; diff --git a/libs/typescript/core/src/telemetry/clients/index.ts b/libs/typescript/core/src/telemetry/clients/index.ts new file mode 100644 index 00000000..cbb1c307 --- /dev/null +++ b/libs/typescript/core/src/telemetry/clients/index.ts @@ -0,0 +1 @@ +export * from "./posthog"; diff --git a/libs/typescript/core/src/telemetry/clients/posthog.ts b/libs/typescript/core/src/telemetry/clients/posthog.ts new file mode 100644 index 00000000..18216c21 --- /dev/null +++ b/libs/typescript/core/src/telemetry/clients/posthog.ts @@ -0,0 +1,309 @@ +/** + * Telemetry client using PostHog for collecting anonymous usage data. + */ + +import { PostHog } from 'posthog-node'; +import { v4 as uuidv4 } from 'uuid'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import * as os from 'node:os'; +import { pino } from 'pino'; +const logger = pino({ name: 'core.telemetry' }); + +// Controls how frequently telemetry will be sent (percentage) +export const TELEMETRY_SAMPLE_RATE = 100; // 100% sampling rate + +// Public PostHog config for anonymous telemetry +// These values are intentionally public and meant for anonymous telemetry only +// https://posthog.com/docs/product-analytics/troubleshooting#is-it-ok-for-my-api-key-to-be-exposed-and-public +export const PUBLIC_POSTHOG_API_KEY = + 'phc_eSkLnbLxsnYFaXksif1ksbrNzYlJShr35miFLDppF14'; +export const PUBLIC_POSTHOG_HOST = 'https://eu.i.posthog.com'; + +export class PostHogTelemetryClient { + private config: { + enabled: boolean; + sampleRate: number; + posthog: { apiKey: string; host: string }; + }; + private installationId: string; + private initialized = false; + private queuedEvents: { + name: string; + properties: Record; + timestamp: number; + }[] = []; + private startTime: number; // seconds + private posthogClient?: PostHog; + private counters: Record = {}; + + constructor() { + // set up config + this.config = { + enabled: true, + sampleRate: TELEMETRY_SAMPLE_RATE, + posthog: { apiKey: PUBLIC_POSTHOG_API_KEY, host: PUBLIC_POSTHOG_HOST }, + }; + // Check for multiple environment variables that can disable telemetry: + // CUA_TELEMETRY=off to disable telemetry (legacy way) + // CUA_TELEMETRY_DISABLED=1 to disable telemetry (new, more explicit way) + const telemetryDisabled = + process.env.CUA_TELEMETRY?.toLowerCase() === 'off' || + ['1', 'true', 'yes', 'on'].includes( + process.env.CUA_TELEMETRY_DISABLED?.toLowerCase() || '' + ); + + this.config.enabled = !telemetryDisabled; + this.config.sampleRate = Number.parseFloat( + process.env.CUA_TELEMETRY_SAMPLE_RATE || String(TELEMETRY_SAMPLE_RATE) + ); + // init client + this.installationId = this._getOrCreateInstallationId(); + this.startTime = Date.now() / 1000; // Convert to seconds + + // Log telemetry status on startup + if (this.config.enabled) { + logger.info(`Telemetry enabled (sampling at ${this.config.sampleRate}%)`); + // Initialize PostHog client if config is available + this._initializePosthog(); + } else { + logger.info('Telemetry disabled'); + } + } + + /** + * Get or create a random installation ID. + * This ID is not tied to any personal information. + */ + private _getOrCreateInstallationId(): string { + const homeDir = os.homedir(); + const idFile = path.join(homeDir, '.cua', 'installation_id'); + + try { + if (fs.existsSync(idFile)) { + return fs.readFileSync(idFile, 'utf-8').trim(); + } + } catch (error) { + logger.debug(`Failed to read installation ID: ${error}`); + } + + // Create new ID if not exists + const newId = uuidv4(); + try { + const dir = path.dirname(idFile); + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } + fs.writeFileSync(idFile, newId); + return newId; + } catch (error) { + logger.debug(`Failed to write installation ID: ${error}`); + } + + // Fallback to in-memory ID if file operations fail + return newId; + } + + /** + * Initialize the PostHog client with configuration. + */ + private _initializePosthog(): boolean { + if (this.initialized) { + return true; + } + + try { + this.posthogClient = new PostHog(this.config.posthog.apiKey, { + host: this.config.posthog.host, + flushAt: 20, // Number of events to batch before sending + flushInterval: 30000, // Send events every 30 seconds + }); + this.initialized = true; + logger.debug('PostHog client initialized successfully'); + + // Process any queued events + this._processQueuedEvents(); + return true; + } catch (error) { + logger.error(`Failed to initialize PostHog client: ${error}`); + return false; + } + } + + /** + * Process any events that were queued before initialization. + */ + private _processQueuedEvents(): void { + if (!this.posthogClient || this.queuedEvents.length === 0) { + return; + } + + for (const event of this.queuedEvents) { + this._captureEvent(event.name, event.properties); + } + this.queuedEvents = []; + } + + /** + * Capture an event with PostHog. + */ + private _captureEvent( + eventName: string, + properties?: Record + ): void { + if (!this.posthogClient) { + return; + } + + try { + // Add standard properties + const eventProperties = { + ...properties, + version: process.env.npm_package_version || 'unknown', + platform: process.platform, + node_version: process.version, + is_ci: this._isCI, + }; + + this.posthogClient.capture({ + distinctId: this.installationId, + event: eventName, + properties: eventProperties, + }); + } catch (error) { + logger.debug(`Failed to capture event: ${error}`); + } + } + + private get _isCI(): boolean { + /** + * Detect if running in CI environment. + */ + return !!( + process.env.CI || + process.env.CONTINUOUS_INTEGRATION || + process.env.GITHUB_ACTIONS || + process.env.GITLAB_CI || + process.env.CIRCLECI || + process.env.TRAVIS || + process.env.JENKINS_URL + ); + } + + increment(counterName: string, value = 1) { + /** + * Increment a named counter. + */ + if (!this.config.enabled) { + return; + } + + if (!(counterName in this.counters)) { + this.counters[counterName] = 0; + } + this.counters[counterName] += value; + } + + recordEvent(eventName: string, properties?: Record): void { + /** + * Record an event with optional properties. + */ + if (!this.config.enabled) { + return; + } + + // Increment counter for this event type + const counterKey = `event:${eventName}`; + this.increment(counterKey); + + // Apply sampling + if (Math.random() * 100 > this.config.sampleRate) { + return; + } + + const event = { + name: eventName, + properties: properties || {}, + timestamp: Date.now() / 1000, + }; + + if (this.initialized && this.posthogClient) { + this._captureEvent(eventName, properties); + } else { + // Queue event if not initialized + this.queuedEvents.push(event); + // Try to initialize again + if (this.config.enabled && !this.initialized) { + this._initializePosthog(); + } + } + } + + /** + * Flush any pending events to PostHog. + */ + async flush(): Promise { + if (!this.config.enabled || !this.posthogClient) { + return false; + } + + try { + // Send counter data as a single event + if (Object.keys(this.counters).length > 0) { + this._captureEvent('telemetry_counters', { + counters: { ...this.counters }, + duration: Date.now() / 1000 - this.startTime, + }); + } + + await this.posthogClient.flush(); + logger.debug('Telemetry flushed successfully'); + + // Clear counters after sending + this.counters = {}; + return true; + } catch (error) { + logger.debug(`Failed to flush telemetry: ${error}`); + return false; + } + } + + enable(): void { + /** + * Enable telemetry collection. + */ + this.config.enabled = true; + logger.info('Telemetry enabled'); + if (!this.initialized) { + this._initializePosthog(); + } + } + + async disable(): Promise { + /** + * Disable telemetry collection. + */ + this.config.enabled = false; + await this.posthogClient?.disable(); + logger.info('Telemetry disabled'); + } + + get enabled(): boolean { + /** + * Check if telemetry is enabled. + */ + return this.config.enabled; + } + + async shutdown(): Promise { + /** + * Shutdown the telemetry client and flush any pending events. + */ + if (this.posthogClient) { + await this.flush(); + await this.posthogClient.shutdown(); + this.initialized = false; + this.posthogClient = undefined; + } + } +} diff --git a/libs/typescript/core/src/telemetry/index.ts b/libs/typescript/core/src/telemetry/index.ts new file mode 100644 index 00000000..44dd951a --- /dev/null +++ b/libs/typescript/core/src/telemetry/index.ts @@ -0,0 +1,7 @@ +/** + * This module provides the core telemetry functionality for CUA libraries. + * + * It provides a low-overhead way to collect anonymous usage data. + */ + +export { PostHogTelemetryClient as Telemetry } from './clients'; diff --git a/libs/typescript/core/tests/index.test.ts b/libs/typescript/core/tests/index.test.ts deleted file mode 100644 index 3ef6cded..00000000 --- a/libs/typescript/core/tests/index.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { expect, test } from 'vitest' -import { myFunction } from '../src' - -test('myFunction', () => { - expect(myFunction()).toBe('Hello, world!') -}) diff --git a/libs/typescript/core/tests/telemetry.test.ts b/libs/typescript/core/tests/telemetry.test.ts new file mode 100644 index 00000000..f8eb780b --- /dev/null +++ b/libs/typescript/core/tests/telemetry.test.ts @@ -0,0 +1,30 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { Telemetry } from '../src/'; + +describe('Telemetry', () => { + let telemetry: Telemetry; + beforeEach(() => { + process.env.CUA_TELEMETRY = ''; + process.env.CUA_TELEMETRY_DISABLED = ''; + telemetry = new Telemetry(); + }); + describe('telemetry.enabled', () => { + it('should return false when CUA_TELEMETRY is off', () => { + process.env.CUA_TELEMETRY = 'off'; + telemetry = new Telemetry(); + expect(telemetry.enabled).toBe(false); + }); + + it('should return true when CUA_TELEMETRY is not set', () => { + process.env.CUA_TELEMETRY = ''; + telemetry = new Telemetry(); + expect(telemetry.enabled).toBe(true); + }); + + it('should return false if CUA_TELEMETRY_DISABLED is 1', () => { + process.env.CUA_TELEMETRY_DISABLED = '1'; + telemetry = new Telemetry(); + expect(telemetry.enabled).toBe(false); + }); + }); +}); diff --git a/libs/typescript/package.json b/libs/typescript/package.json new file mode 100644 index 00000000..f14aa59e --- /dev/null +++ b/libs/typescript/package.json @@ -0,0 +1,21 @@ +{ + "name": "cua-ts", + "version": "1.0.0", + "description": "The c/ua typescript libs.", + "keywords": [], + "author": "c/ua", + "license": "MIT", + "scripts": { + "lint": "biome lint .", + "lint:fix": "biome lint --fix ." + }, + "packageManager": "pnpm@10.6.5", + "devDependencies": { + "@biomejs/biome": "^1.9.4" + }, + "pnpm": { + "onlyBuiltDependencies": [ + "@biomejs/biome" + ] + } +} \ No newline at end of file diff --git a/libs/typescript/pnpm-lock.yaml b/libs/typescript/pnpm-lock.yaml new file mode 100644 index 00000000..26c84528 --- /dev/null +++ b/libs/typescript/pnpm-lock.yaml @@ -0,0 +1,105 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@biomejs/biome': + specifier: ^1.9.4 + version: 1.9.4 + +packages: + + '@biomejs/biome@1.9.4': + resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@1.9.4': + resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@1.9.4': + resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@1.9.4': + resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@1.9.4': + resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@1.9.4': + resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@1.9.4': + resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@1.9.4': + resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@1.9.4': + resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + +snapshots: + + '@biomejs/biome@1.9.4': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 1.9.4 + '@biomejs/cli-darwin-x64': 1.9.4 + '@biomejs/cli-linux-arm64': 1.9.4 + '@biomejs/cli-linux-arm64-musl': 1.9.4 + '@biomejs/cli-linux-x64': 1.9.4 + '@biomejs/cli-linux-x64-musl': 1.9.4 + '@biomejs/cli-win32-arm64': 1.9.4 + '@biomejs/cli-win32-x64': 1.9.4 + + '@biomejs/cli-darwin-arm64@1.9.4': + optional: true + + '@biomejs/cli-darwin-x64@1.9.4': + optional: true + + '@biomejs/cli-linux-arm64-musl@1.9.4': + optional: true + + '@biomejs/cli-linux-arm64@1.9.4': + optional: true + + '@biomejs/cli-linux-x64-musl@1.9.4': + optional: true + + '@biomejs/cli-linux-x64@1.9.4': + optional: true + + '@biomejs/cli-win32-arm64@1.9.4': + optional: true + + '@biomejs/cli-win32-x64@1.9.4': + optional: true From 507a9a9454d99bf9a66572170a995ee8d4b32374 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Mon, 23 Jun 2025 16:11:59 -0700 Subject: [PATCH 099/141] Add telemetry --- .../computer/src/computer/providers/base.ts | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/libs/typescript/computer/src/computer/providers/base.ts b/libs/typescript/computer/src/computer/providers/base.ts index fc91b773..d706e725 100644 --- a/libs/typescript/computer/src/computer/providers/base.ts +++ b/libs/typescript/computer/src/computer/providers/base.ts @@ -1,9 +1,10 @@ -import { Telemetry } from "@cua/core"; -import type { OSType } from "../../types"; -import type { BaseComputerConfig, Display, VMProviderType } from "../types"; -import pino from "pino"; +import { Telemetry } from '@cua/core'; +import type { OSType } from '../../types'; +import type { BaseComputerConfig, Display, VMProviderType } from '../types'; +import pino from 'pino'; +import os from 'node:os'; -const logger = pino({ name: "computer-base" }); +const logger = pino({ name: 'computer-base' }); /** * Base Computer class with shared functionality @@ -12,18 +13,23 @@ export abstract class BaseComputer { protected name: string; protected osType: OSType; protected vmProvider?: VMProviderType; - protected telemetry: Telemetry + protected telemetry: Telemetry; constructor(config: BaseComputerConfig) { this.name = config.name; this.osType = config.osType; this.telemetry = new Telemetry(); - this.telemetry.recordEvent('module_init', { - module: "computer", + this.telemetry.recordEvent('module_init', { + module: 'computer', version: process.env.npm_package_version, node_version: process.version, - }, -) + }); + + this.telemetry.recordEvent('computer_initialized', { + os: os.platform(), + os_version: os.version(), + node_version: process.version, + }); } /** @@ -100,10 +106,10 @@ export abstract class BaseComputer { } const value = Number.parseFloat(match[1]); - const unit = match[2] || "MB"; // Default to MB if no unit specified + const unit = match[2] || 'MB'; // Default to MB if no unit specified // Convert to MB - if (unit === "GB") { + if (unit === 'GB') { return Math.round(value * 1024); } return Math.round(value); From 6401c12812551ff9d5851e19f9a9d4f4b4b78d2f Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Mon, 23 Jun 2025 16:19:25 -0700 Subject: [PATCH 100/141] Fix linting --- libs/typescript/biome.json | 16 +- .../typescript/computer/src/computer/index.ts | 2 +- .../computer/src/computer/providers/base.ts | 4 +- .../computer/src/computer/providers/cloud.ts | 24 +- .../computer/src/computer/providers/index.ts | 4 +- .../typescript/computer/src/computer/types.ts | 4 +- libs/typescript/computer/src/index.ts | 4 +- .../typescript/computer/src/interface/base.ts | 56 +-- .../computer/src/interface/factory.ts | 28 +- .../computer/src/interface/index.ts | 12 +- .../computer/src/interface/linux.ts | 2 +- .../computer/src/interface/macos.ts | 135 ++++--- .../computer/src/interface/windows.ts | 2 +- libs/typescript/computer/src/types.ts | 6 +- .../computer/tests/computer/cloud.test.ts | 14 +- .../computer/tests/interface/factory.test.ts | 40 +- .../computer/tests/interface/index.test.ts | 32 +- .../computer/tests/interface/linux.test.ts | 22 +- .../computer/tests/interface/macos.test.ts | 362 +++++++++--------- .../computer/tests/interface/windows.test.ts | 22 +- libs/typescript/computer/tests/setup.ts | 2 +- libs/typescript/computer/tsdown.config.ts | 8 +- libs/typescript/computer/vitest.config.ts | 6 +- .../core/src/telemetry/clients/index.ts | 2 +- .../core/src/telemetry/clients/posthog.ts | 8 +- libs/typescript/core/tests/telemetry.test.ts | 2 +- libs/typescript/core/tsdown.config.ts | 4 +- libs/typescript/core/vitest.config.ts | 4 +- libs/typescript/package.json | 4 +- 29 files changed, 418 insertions(+), 413 deletions(-) diff --git a/libs/typescript/biome.json b/libs/typescript/biome.json index 3251a52d..a0394eff 100644 --- a/libs/typescript/biome.json +++ b/libs/typescript/biome.json @@ -7,10 +7,8 @@ }, "files": { "ignoreUnknown": false, - "ignore": [ - "dist", - "node_modules" - ] + "include": ["core/**/*.ts", "computer/**/*.ts"], + "ignore": ["dist", "node_modules"] }, "formatter": { "enabled": true, @@ -59,12 +57,8 @@ "level": "warn", "fix": "safe", "options": { - "attributes": [ - "className" - ], - "functions": [ - "cn" - ] + "attributes": ["className"], + "functions": ["cn"] } } } @@ -83,4 +77,4 @@ "bracketSpacing": true } } -} \ No newline at end of file +} diff --git a/libs/typescript/computer/src/computer/index.ts b/libs/typescript/computer/src/computer/index.ts index 4a1a7007..d7411b63 100644 --- a/libs/typescript/computer/src/computer/index.ts +++ b/libs/typescript/computer/src/computer/index.ts @@ -1 +1 @@ -export { BaseComputer, CloudComputer } from "./providers"; +export { BaseComputer, CloudComputer } from './providers'; diff --git a/libs/typescript/computer/src/computer/providers/base.ts b/libs/typescript/computer/src/computer/providers/base.ts index d706e725..6b94021d 100644 --- a/libs/typescript/computer/src/computer/providers/base.ts +++ b/libs/typescript/computer/src/computer/providers/base.ts @@ -1,8 +1,8 @@ +import os from 'node:os'; import { Telemetry } from '@cua/core'; +import pino from 'pino'; import type { OSType } from '../../types'; import type { BaseComputerConfig, Display, VMProviderType } from '../types'; -import pino from 'pino'; -import os from 'node:os'; const logger = pino({ name: 'computer-base' }); diff --git a/libs/typescript/computer/src/computer/providers/cloud.ts b/libs/typescript/computer/src/computer/providers/cloud.ts index a1e458dc..b3854161 100644 --- a/libs/typescript/computer/src/computer/providers/cloud.ts +++ b/libs/typescript/computer/src/computer/providers/cloud.ts @@ -1,12 +1,12 @@ -import { BaseComputer } from "./base"; -import type { CloudComputerConfig, VMProviderType } from "../types"; +import pino from 'pino'; import { - InterfaceFactory, type BaseComputerInterface, -} from "../../interface/index"; -import pino from "pino"; + InterfaceFactory, +} from '../../interface/index'; +import type { CloudComputerConfig, VMProviderType } from '../types'; +import { BaseComputer } from './base'; -const logger = pino({ name: "computer-cloud" }); +const logger = pino({ name: 'computer-cloud' }); /** * Cloud-specific computer implementation @@ -31,7 +31,7 @@ export class CloudComputer extends BaseComputer { */ async run(): Promise { if (this.initialized) { - logger.info("Computer already initialized, skipping initialization"); + logger.info('Computer already initialized, skipping initialization'); return; } @@ -49,11 +49,11 @@ export class CloudComputer extends BaseComputer { ); // Wait for the interface to be ready - logger.info("Waiting for interface to be ready..."); + logger.info('Waiting for interface to be ready...'); await this.iface.waitForReady(); this.initialized = true; - logger.info("Cloud computer ready"); + logger.info('Cloud computer ready'); } catch (error) { logger.error(`Failed to initialize cloud computer: ${error}`); throw new Error(`Failed to initialize cloud computer: ${error}`); @@ -64,7 +64,7 @@ export class CloudComputer extends BaseComputer { * Stop the cloud computer (disconnect interface) */ async stop(): Promise { - logger.info("Disconnecting from cloud computer..."); + logger.info('Disconnecting from cloud computer...'); if (this.iface) { this.iface.disconnect(); @@ -72,7 +72,7 @@ export class CloudComputer extends BaseComputer { } this.initialized = false; - logger.info("Disconnected from cloud computer"); + logger.info('Disconnected from cloud computer'); } /** @@ -80,7 +80,7 @@ export class CloudComputer extends BaseComputer { */ get interface(): BaseComputerInterface { if (!this.iface) { - throw new Error("Computer not initialized. Call run() first."); + throw new Error('Computer not initialized. Call run() first.'); } return this.iface; } diff --git a/libs/typescript/computer/src/computer/providers/index.ts b/libs/typescript/computer/src/computer/providers/index.ts index d4fd5391..27faf7d6 100644 --- a/libs/typescript/computer/src/computer/providers/index.ts +++ b/libs/typescript/computer/src/computer/providers/index.ts @@ -1,2 +1,2 @@ -export * from "./base"; -export * from "./cloud"; \ No newline at end of file +export * from './base'; +export * from './cloud'; diff --git a/libs/typescript/computer/src/computer/types.ts b/libs/typescript/computer/src/computer/types.ts index 296302f3..7bca918b 100644 --- a/libs/typescript/computer/src/computer/types.ts +++ b/libs/typescript/computer/src/computer/types.ts @@ -1,4 +1,4 @@ -import type { OSType, ScreenSize } from "../types"; +import type { OSType, ScreenSize } from '../types'; /** * Display configuration for the computer. @@ -32,5 +32,5 @@ export interface CloudComputerConfig extends BaseComputerConfig { } export enum VMProviderType { - CLOUD = "cloud", + CLOUD = 'cloud', } diff --git a/libs/typescript/computer/src/index.ts b/libs/typescript/computer/src/index.ts index 9917d559..44b515fe 100644 --- a/libs/typescript/computer/src/index.ts +++ b/libs/typescript/computer/src/index.ts @@ -1,6 +1,6 @@ // Export classes -export { CloudComputer as Computer } from "./computer"; +export { CloudComputer as Computer } from './computer'; //todo: figure out what types to export and how to do that // -export { OSType } from "./types"; +export { OSType } from './types'; diff --git a/libs/typescript/computer/src/interface/base.ts b/libs/typescript/computer/src/interface/base.ts index a69002d4..061ce599 100644 --- a/libs/typescript/computer/src/interface/base.ts +++ b/libs/typescript/computer/src/interface/base.ts @@ -2,11 +2,11 @@ * Base interface for computer control. */ -import type { ScreenSize } from "../types"; -import WebSocket from "ws"; -import pino from "pino"; +import pino from 'pino'; +import WebSocket from 'ws'; +import type { ScreenSize } from '../types'; -export type MouseButton = "left" | "middle" | "right"; +export type MouseButton = 'left' | 'middle' | 'right'; export interface CursorPosition { x: number; @@ -40,12 +40,12 @@ export abstract class BaseComputerInterface { protected apiKey?: string; protected vmName?: string; - protected logger = pino({ name: "interface-base" }); + protected logger = pino({ name: 'interface-base' }); constructor( ipAddress: string, - username = "lume", - password = "lume", + username = 'lume', + password = 'lume', apiKey?: string, vmName?: string ) { @@ -58,8 +58,8 @@ export abstract class BaseComputerInterface { // Initialize WebSocket with headers if needed const headers: { [key: string]: string } = {}; if (this.apiKey && this.vmName) { - headers["X-API-Key"] = this.apiKey; - headers["X-VM-Name"] = this.vmName; + headers['X-API-Key'] = this.apiKey; + headers['X-VM-Name'] = this.vmName; } // Create the WebSocket instance @@ -71,15 +71,15 @@ export abstract class BaseComputerInterface { * Subclasses can override this to customize the URI. */ protected get wsUri(): string { - const protocol = this.apiKey ? "wss" : "ws"; + const protocol = this.apiKey ? 'wss' : 'ws'; // Check if ipAddress already includes a port - if (this.ipAddress.includes(":")) { + if (this.ipAddress.includes(':')) { return `${protocol}://${this.ipAddress}/ws`; } // Otherwise, append the default port - const port = this.apiKey ? "8443" : "8000"; + const port = this.apiKey ? '8443' : '8000'; return `${protocol}://${this.ipAddress}:${port}/ws`; } @@ -115,9 +115,9 @@ export abstract class BaseComputerInterface { if (this.ws.readyState === WebSocket.OPEN) { // send authentication message if needed if (this.apiKey && this.vmName) { - this.logger.info("Performing authentication handshake..."); + this.logger.info('Performing authentication handshake...'); const authMessage = { - command: "authenticate", + command: 'authenticate', params: { api_key: this.apiKey, container_name: this.vmName, @@ -129,22 +129,22 @@ export abstract class BaseComputerInterface { try { const authResult = JSON.parse(data.toString()); if (!authResult.success) { - const errorMsg = authResult.error || "Authentication failed"; + const errorMsg = authResult.error || 'Authentication failed'; this.logger.error(`Authentication failed: ${errorMsg}`); this.ws.close(); reject(new Error(`Authentication failed: ${errorMsg}`)); } else { - this.logger.info("Authentication successful"); - this.ws.off("message", authHandler); + this.logger.info('Authentication successful'); + this.ws.off('message', authHandler); resolve(); } } catch (error) { - this.ws.off("message", authHandler); + this.ws.off('message', authHandler); reject(error); } }; - this.ws.on("message", authHandler); + this.ws.on('message', authHandler); this.ws.send(JSON.stringify(authMessage)); }); } @@ -159,8 +159,8 @@ export abstract class BaseComputerInterface { ) { const headers: { [key: string]: string } = {}; if (this.apiKey && this.vmName) { - headers["X-API-Key"] = this.apiKey; - headers["X-VM-Name"] = this.vmName; + headers['X-API-Key'] = this.apiKey; + headers['X-VM-Name'] = this.vmName; } this.ws = new WebSocket(this.wsUri, { headers }); } @@ -168,23 +168,23 @@ export abstract class BaseComputerInterface { return new Promise((resolve, reject) => { // If already connecting, wait for it to complete if (this.ws.readyState === WebSocket.CONNECTING) { - this.ws.addEventListener("open", () => resolve(), { once: true }); - this.ws.addEventListener("error", (error) => reject(error), { + this.ws.addEventListener('open', () => resolve(), { once: true }); + this.ws.addEventListener('error', (error) => reject(error), { once: true, }); return; } // Set up event handlers - this.ws.on("open", () => { + this.ws.on('open', () => { resolve(); }); - this.ws.on("error", (error: Error) => { + this.ws.on('error', (error: Error) => { reject(error); }); - this.ws.on("close", () => { + this.ws.on('close', () => { if (!this.closed) { // Attempt to reconnect setTimeout(() => this.connect(), 1000); @@ -224,10 +224,10 @@ export abstract class BaseComputerInterface { } catch (error) { innerReject(error); } - this.ws.off("message", messageHandler); + this.ws.off('message', messageHandler); }; - this.ws.on("message", messageHandler); + this.ws.on('message', messageHandler); const wsCommand = { command, params }; this.ws.send(JSON.stringify(wsCommand)); } diff --git a/libs/typescript/computer/src/interface/factory.ts b/libs/typescript/computer/src/interface/factory.ts index 9621d45f..60864f0e 100644 --- a/libs/typescript/computer/src/interface/factory.ts +++ b/libs/typescript/computer/src/interface/factory.ts @@ -2,11 +2,11 @@ * Factory for creating computer interfaces. */ -import type { BaseComputerInterface } from "./base"; -import { MacOSComputerInterface } from "./macos"; -import { LinuxComputerInterface } from "./linux"; -import { WindowsComputerInterface } from "./windows"; -import type { OSType } from "../types"; +import type { OSType } from '../types'; +import type { BaseComputerInterface } from './base'; +import { LinuxComputerInterface } from './linux'; +import { MacOSComputerInterface } from './macos'; +import { WindowsComputerInterface } from './windows'; export const InterfaceFactory = { /** @@ -26,27 +26,27 @@ export const InterfaceFactory = { vmName?: string ): BaseComputerInterface { switch (os) { - case "macos": + case 'macos': return new MacOSComputerInterface( ipAddress, - "lume", - "lume", + 'lume', + 'lume', apiKey, vmName ); - case "linux": + case 'linux': return new LinuxComputerInterface( ipAddress, - "lume", - "lume", + 'lume', + 'lume', apiKey, vmName ); - case "windows": + case 'windows': return new WindowsComputerInterface( ipAddress, - "lume", - "lume", + 'lume', + 'lume', apiKey, vmName ); diff --git a/libs/typescript/computer/src/interface/index.ts b/libs/typescript/computer/src/interface/index.ts index 84141da8..285f594b 100644 --- a/libs/typescript/computer/src/interface/index.ts +++ b/libs/typescript/computer/src/interface/index.ts @@ -1,6 +1,6 @@ -export { BaseComputerInterface } from "./base"; -export type { MouseButton, CursorPosition, AccessibilityNode } from "./base"; -export { InterfaceFactory } from "./factory"; -export { MacOSComputerInterface } from "./macos"; -export { LinuxComputerInterface } from "./linux"; -export { WindowsComputerInterface } from "./windows"; +export { BaseComputerInterface } from './base'; +export type { MouseButton, CursorPosition, AccessibilityNode } from './base'; +export { InterfaceFactory } from './factory'; +export { MacOSComputerInterface } from './macos'; +export { LinuxComputerInterface } from './linux'; +export { WindowsComputerInterface } from './windows'; diff --git a/libs/typescript/computer/src/interface/linux.ts b/libs/typescript/computer/src/interface/linux.ts index 45717588..5fcf27c3 100644 --- a/libs/typescript/computer/src/interface/linux.ts +++ b/libs/typescript/computer/src/interface/linux.ts @@ -2,7 +2,7 @@ * Linux computer interface implementation. */ -import { MacOSComputerInterface } from "./macos"; +import { MacOSComputerInterface } from './macos'; /** * Linux interface implementation. diff --git a/libs/typescript/computer/src/interface/macos.ts b/libs/typescript/computer/src/interface/macos.ts index d34ed984..3c41e03f 100644 --- a/libs/typescript/computer/src/interface/macos.ts +++ b/libs/typescript/computer/src/interface/macos.ts @@ -2,227 +2,236 @@ * macOS computer interface implementation. */ -import { BaseComputerInterface } from "./base"; -import type { MouseButton, CursorPosition, AccessibilityNode } from "./base"; -import type { ScreenSize } from "../types"; +import type { ScreenSize } from '../types'; +import { BaseComputerInterface } from './base'; +import type { AccessibilityNode, CursorPosition, MouseButton } from './base'; export class MacOSComputerInterface extends BaseComputerInterface { // Mouse Actions async mouseDown( x?: number, y?: number, - button: MouseButton = "left" + button: MouseButton = 'left' ): Promise { - await this.sendCommand("mouse_down", { x, y, button }); + await this.sendCommand('mouse_down', { x, y, button }); } async mouseUp( x?: number, y?: number, - button: MouseButton = "left" + button: MouseButton = 'left' ): Promise { - await this.sendCommand("mouse_up", { x, y, button }); + await this.sendCommand('mouse_up', { x, y, button }); } async leftClick(x?: number, y?: number): Promise { - await this.sendCommand("left_click", { x, y }); + await this.sendCommand('left_click', { x, y }); } async rightClick(x?: number, y?: number): Promise { - await this.sendCommand("right_click", { x, y }); + await this.sendCommand('right_click', { x, y }); } async doubleClick(x?: number, y?: number): Promise { - await this.sendCommand("double_click", { x, y }); + await this.sendCommand('double_click', { x, y }); } async moveCursor(x: number, y: number): Promise { - await this.sendCommand("move_cursor", { x, y }); + await this.sendCommand('move_cursor', { x, y }); } async dragTo( x: number, y: number, - button: MouseButton = "left", + button: MouseButton = 'left', duration = 0.5 ): Promise { - await this.sendCommand("drag_to", { x, y, button, duration }); + await this.sendCommand('drag_to', { x, y, button, duration }); } async drag( path: Array<[number, number]>, - button: MouseButton = "left", + button: MouseButton = 'left', duration = 0.5 ): Promise { - await this.sendCommand("drag", { path, button, duration }); + await this.sendCommand('drag', { path, button, duration }); } // Keyboard Actions async keyDown(key: string): Promise { - await this.sendCommand("key_down", { key }); + await this.sendCommand('key_down', { key }); } async keyUp(key: string): Promise { - await this.sendCommand("key_up", { key }); + await this.sendCommand('key_up', { key }); } async typeText(text: string): Promise { - await this.sendCommand("type_text", { text }); + await this.sendCommand('type_text', { text }); } async pressKey(key: string): Promise { - await this.sendCommand("press_key", { key }); + await this.sendCommand('press_key', { key }); } async hotkey(...keys: string[]): Promise { - await this.sendCommand("hotkey", { keys }); + await this.sendCommand('hotkey', { keys }); } // Scrolling Actions async scroll(x: number, y: number): Promise { - await this.sendCommand("scroll", { x, y }); + await this.sendCommand('scroll', { x, y }); } async scrollDown(clicks = 1): Promise { - await this.sendCommand("scroll_down", { clicks }); + await this.sendCommand('scroll_down', { clicks }); } async scrollUp(clicks = 1): Promise { - await this.sendCommand("scroll_up", { clicks }); + await this.sendCommand('scroll_up', { clicks }); } // Screen Actions async screenshot(): Promise { - const response = await this.sendCommand("screenshot"); + const response = await this.sendCommand('screenshot'); if (!response.image_data) { - throw new Error("Failed to take screenshot"); + throw new Error('Failed to take screenshot'); } - return Buffer.from(response.image_data as string, "base64"); + return Buffer.from(response.image_data as string, 'base64'); } async getScreenSize(): Promise { - const response = await this.sendCommand("get_screen_size"); + const response = await this.sendCommand('get_screen_size'); if (!response.success || !response.size) { - throw new Error("Failed to get screen size"); + throw new Error('Failed to get screen size'); } return response.size as ScreenSize; } async getCursorPosition(): Promise { - const response = await this.sendCommand("get_cursor_position"); + const response = await this.sendCommand('get_cursor_position'); if (!response.success || !response.position) { - throw new Error("Failed to get cursor position"); + throw new Error('Failed to get cursor position'); } return response.position as CursorPosition; } // Clipboard Actions async copyToClipboard(): Promise { - const response = await this.sendCommand("copy_to_clipboard"); + const response = await this.sendCommand('copy_to_clipboard'); if (!response.success || !response.content) { - throw new Error("Failed to get clipboard content"); + throw new Error('Failed to get clipboard content'); } return response.content as string; } async setClipboard(text: string): Promise { - await this.sendCommand("set_clipboard", { text }); + await this.sendCommand('set_clipboard', { text }); } // File System Actions async fileExists(path: string): Promise { - const response = await this.sendCommand("file_exists", { path }); + const response = await this.sendCommand('file_exists', { path }); return (response.exists as boolean) || false; } async directoryExists(path: string): Promise { - const response = await this.sendCommand("directory_exists", { path }); + const response = await this.sendCommand('directory_exists', { path }); return (response.exists as boolean) || false; } async listDir(path: string): Promise { - const response = await this.sendCommand("list_dir", { path }); + const response = await this.sendCommand('list_dir', { path }); if (!response.success) { - throw new Error(response.error as string || "Failed to list directory"); + throw new Error((response.error as string) || 'Failed to list directory'); } return (response.files as string[]) || []; } async readText(path: string): Promise { - const response = await this.sendCommand("read_text", { path }); + const response = await this.sendCommand('read_text', { path }); if (!response.success) { - throw new Error(response.error as string || "Failed to read file"); + throw new Error((response.error as string) || 'Failed to read file'); } - return (response.content as string) || ""; + return (response.content as string) || ''; } async writeText(path: string, content: string): Promise { - const response = await this.sendCommand("write_text", { path, content }); + const response = await this.sendCommand('write_text', { path, content }); if (!response.success) { - throw new Error(response.error as string || "Failed to write file"); + throw new Error((response.error as string) || 'Failed to write file'); } } async readBytes(path: string): Promise { - const response = await this.sendCommand("read_bytes", { path }); + const response = await this.sendCommand('read_bytes', { path }); if (!response.success) { - throw new Error(response.error as string || "Failed to read file"); + throw new Error((response.error as string) || 'Failed to read file'); } - return Buffer.from(response.content_b64 as string, "base64"); + return Buffer.from(response.content_b64 as string, 'base64'); } async writeBytes(path: string, content: Buffer): Promise { - const response = await this.sendCommand("write_bytes", { + const response = await this.sendCommand('write_bytes', { path, - content_b64: content.toString("base64"), + content_b64: content.toString('base64'), }); if (!response.success) { - throw new Error(response.error as string || "Failed to write file"); + throw new Error((response.error as string) || 'Failed to write file'); } } async deleteFile(path: string): Promise { - const response = await this.sendCommand("delete_file", { path }); + const response = await this.sendCommand('delete_file', { path }); if (!response.success) { - throw new Error(response.error as string || "Failed to delete file"); + throw new Error((response.error as string) || 'Failed to delete file'); } } async createDir(path: string): Promise { - const response = await this.sendCommand("create_dir", { path }); + const response = await this.sendCommand('create_dir', { path }); if (!response.success) { - throw new Error(response.error as string || "Failed to create directory"); + throw new Error( + (response.error as string) || 'Failed to create directory' + ); } } async deleteDir(path: string): Promise { - const response = await this.sendCommand("delete_dir", { path }); + const response = await this.sendCommand('delete_dir', { path }); if (!response.success) { - throw new Error(response.error as string || "Failed to delete directory"); + throw new Error( + (response.error as string) || 'Failed to delete directory' + ); } } async runCommand(command: string): Promise<[string, string]> { - const response = await this.sendCommand("run_command", { command }); + const response = await this.sendCommand('run_command', { command }); if (!response.success) { - throw new Error(response.error as string || "Failed to run command"); + throw new Error((response.error as string) || 'Failed to run command'); } - return [(response.stdout as string) || "", (response.stderr as string) || ""]; + return [ + (response.stdout as string) || '', + (response.stderr as string) || '', + ]; } // Accessibility Actions async getAccessibilityTree(): Promise { - const response = await this.sendCommand("get_accessibility_tree"); + const response = await this.sendCommand('get_accessibility_tree'); if (!response.success) { - throw new Error(response.error as string || "Failed to get accessibility tree"); + throw new Error( + (response.error as string) || 'Failed to get accessibility tree' + ); } return response as unknown as AccessibilityNode; } async toScreenCoordinates(x: number, y: number): Promise<[number, number]> { - const response = await this.sendCommand("to_screen_coordinates", { x, y }); + const response = await this.sendCommand('to_screen_coordinates', { x, y }); if (!response.success || !response.coordinates) { - throw new Error("Failed to convert to screen coordinates"); + throw new Error('Failed to convert to screen coordinates'); } return response.coordinates as [number, number]; } @@ -231,12 +240,12 @@ export class MacOSComputerInterface extends BaseComputerInterface { x: number, y: number ): Promise<[number, number]> { - const response = await this.sendCommand("to_screenshot_coordinates", { + const response = await this.sendCommand('to_screenshot_coordinates', { x, y, }); if (!response.success || !response.coordinates) { - throw new Error("Failed to convert to screenshot coordinates"); + throw new Error('Failed to convert to screenshot coordinates'); } return response.coordinates as [number, number]; } diff --git a/libs/typescript/computer/src/interface/windows.ts b/libs/typescript/computer/src/interface/windows.ts index 8fee3147..c9f138d1 100644 --- a/libs/typescript/computer/src/interface/windows.ts +++ b/libs/typescript/computer/src/interface/windows.ts @@ -2,7 +2,7 @@ * Windows computer interface implementation. */ -import { MacOSComputerInterface } from "./macos"; +import { MacOSComputerInterface } from './macos'; /** * Windows interface implementation. diff --git a/libs/typescript/computer/src/types.ts b/libs/typescript/computer/src/types.ts index b6851b41..f8a175fe 100644 --- a/libs/typescript/computer/src/types.ts +++ b/libs/typescript/computer/src/types.ts @@ -1,7 +1,7 @@ export enum OSType { - MACOS = "macos", - WINDOWS = "windows", - LINUX = "linux", + MACOS = 'macos', + WINDOWS = 'windows', + LINUX = 'linux', } export interface ScreenSize { diff --git a/libs/typescript/computer/tests/computer/cloud.test.ts b/libs/typescript/computer/tests/computer/cloud.test.ts index f1f7a1b6..9b927d57 100644 --- a/libs/typescript/computer/tests/computer/cloud.test.ts +++ b/libs/typescript/computer/tests/computer/cloud.test.ts @@ -1,12 +1,12 @@ -import { describe, expect, it } from "vitest"; -import { OSType } from "../../src/types"; -import { Computer } from "../../src"; +import { describe, expect, it } from 'vitest'; +import { Computer } from '../../src'; +import { OSType } from '../../src/types'; -describe("Computer Cloud", () => { - it("Should create computer instance", () => { +describe('Computer Cloud', () => { + it('Should create computer instance', () => { const cloud = new Computer({ - apiKey: "asdf", - name: "s-linux-1234", + apiKey: 'asdf', + name: 's-linux-1234', osType: OSType.LINUX, }); expect(cloud).toBeInstanceOf(Computer); diff --git a/libs/typescript/computer/tests/interface/factory.test.ts b/libs/typescript/computer/tests/interface/factory.test.ts index 8aa1a1d7..e5f3296a 100644 --- a/libs/typescript/computer/tests/interface/factory.test.ts +++ b/libs/typescript/computer/tests/interface/factory.test.ts @@ -1,21 +1,21 @@ -import { describe, expect, it } from "vitest"; -import { InterfaceFactory } from "../../src/interface/factory.ts"; -import { MacOSComputerInterface } from "../../src/interface/macos.ts"; -import { LinuxComputerInterface } from "../../src/interface/linux.ts"; -import { WindowsComputerInterface } from "../../src/interface/windows.ts"; -import { OSType } from "../../src/types.ts"; +import { describe, expect, it } from 'vitest'; +import { InterfaceFactory } from '../../src/interface/factory.ts'; +import { LinuxComputerInterface } from '../../src/interface/linux.ts'; +import { MacOSComputerInterface } from '../../src/interface/macos.ts'; +import { WindowsComputerInterface } from '../../src/interface/windows.ts'; +import { OSType } from '../../src/types.ts'; -describe("InterfaceFactory", () => { +describe('InterfaceFactory', () => { const testParams = { - ipAddress: "192.168.1.100", - username: "testuser", - password: "testpass", - apiKey: "test-api-key", - vmName: "test-vm", + ipAddress: '192.168.1.100', + username: 'testuser', + password: 'testpass', + apiKey: 'test-api-key', + vmName: 'test-vm', }; - describe("createInterfaceForOS", () => { - it("should create MacOSComputerInterface for macOS", () => { + describe('createInterfaceForOS', () => { + it('should create MacOSComputerInterface for macOS', () => { const interface_ = InterfaceFactory.createInterfaceForOS( OSType.MACOS, testParams.ipAddress, @@ -26,7 +26,7 @@ describe("InterfaceFactory", () => { expect(interface_).toBeInstanceOf(MacOSComputerInterface); }); - it("should create LinuxComputerInterface for Linux", () => { + it('should create LinuxComputerInterface for Linux', () => { const interface_ = InterfaceFactory.createInterfaceForOS( OSType.LINUX, testParams.ipAddress, @@ -37,7 +37,7 @@ describe("InterfaceFactory", () => { expect(interface_).toBeInstanceOf(LinuxComputerInterface); }); - it("should create WindowsComputerInterface for Windows", () => { + it('should create WindowsComputerInterface for Windows', () => { const interface_ = InterfaceFactory.createInterfaceForOS( OSType.WINDOWS, testParams.ipAddress, @@ -48,18 +48,18 @@ describe("InterfaceFactory", () => { expect(interface_).toBeInstanceOf(WindowsComputerInterface); }); - it("should throw error for unsupported OS type", () => { + it('should throw error for unsupported OS type', () => { expect(() => { InterfaceFactory.createInterfaceForOS( - "unsupported" as OSType, + 'unsupported' as OSType, testParams.ipAddress, testParams.apiKey, testParams.vmName ); - }).toThrow("Unsupported OS type: unsupported"); + }).toThrow('Unsupported OS type: unsupported'); }); - it("should create interface without API key and VM name", () => { + it('should create interface without API key and VM name', () => { const interface_ = InterfaceFactory.createInterfaceForOS( OSType.MACOS, testParams.ipAddress diff --git a/libs/typescript/computer/tests/interface/index.test.ts b/libs/typescript/computer/tests/interface/index.test.ts index 0c8264ab..1a0cb8ef 100644 --- a/libs/typescript/computer/tests/interface/index.test.ts +++ b/libs/typescript/computer/tests/interface/index.test.ts @@ -1,35 +1,37 @@ -import { describe, expect, it } from "vitest"; -import * as InterfaceExports from "../../src/interface/index.ts"; +import { describe, expect, it } from 'vitest'; +import * as InterfaceExports from '../../src/interface/index.ts'; -describe("Interface Module Exports", () => { - it("should export InterfaceFactory", () => { +describe('Interface Module Exports', () => { + it('should export InterfaceFactory', () => { expect(InterfaceExports.InterfaceFactory).toBeDefined(); - expect(InterfaceExports.InterfaceFactory.createInterfaceForOS).toBeDefined(); + expect( + InterfaceExports.InterfaceFactory.createInterfaceForOS + ).toBeDefined(); }); - it("should export BaseComputerInterface", () => { + it('should export BaseComputerInterface', () => { expect(InterfaceExports.BaseComputerInterface).toBeDefined(); }); - it("should export MacOSComputerInterface", () => { + it('should export MacOSComputerInterface', () => { expect(InterfaceExports.MacOSComputerInterface).toBeDefined(); }); - it("should export LinuxComputerInterface", () => { + it('should export LinuxComputerInterface', () => { expect(InterfaceExports.LinuxComputerInterface).toBeDefined(); }); - it("should export WindowsComputerInterface", () => { + it('should export WindowsComputerInterface', () => { expect(InterfaceExports.WindowsComputerInterface).toBeDefined(); }); - it("should export all expected interfaces", () => { + it('should export all expected interfaces', () => { const expectedExports = [ - "InterfaceFactory", - "BaseComputerInterface", - "MacOSComputerInterface", - "LinuxComputerInterface", - "WindowsComputerInterface", + 'InterfaceFactory', + 'BaseComputerInterface', + 'MacOSComputerInterface', + 'LinuxComputerInterface', + 'WindowsComputerInterface', ]; const actualExports = Object.keys(InterfaceExports); diff --git a/libs/typescript/computer/tests/interface/linux.test.ts b/libs/typescript/computer/tests/interface/linux.test.ts index c82627c7..ae086fcf 100644 --- a/libs/typescript/computer/tests/interface/linux.test.ts +++ b/libs/typescript/computer/tests/interface/linux.test.ts @@ -1,18 +1,18 @@ -import { describe, expect, it } from "vitest"; -import { LinuxComputerInterface } from "../../src/interface/linux.ts"; -import { MacOSComputerInterface } from "../../src/interface/macos.ts"; +import { describe, expect, it } from 'vitest'; +import { LinuxComputerInterface } from '../../src/interface/linux.ts'; +import { MacOSComputerInterface } from '../../src/interface/macos.ts'; -describe("LinuxComputerInterface", () => { +describe('LinuxComputerInterface', () => { const testParams = { - ipAddress: "test.cua.com", // TEST-NET-1 address (RFC 5737) - guaranteed not to be routable - username: "testuser", - password: "testpass", - apiKey: "test-api-key", - vmName: "test-vm", + ipAddress: 'test.cua.com', // TEST-NET-1 address (RFC 5737) - guaranteed not to be routable + username: 'testuser', + password: 'testpass', + apiKey: 'test-api-key', + vmName: 'test-vm', }; - describe("Inheritance", () => { - it("should extend MacOSComputerInterface", () => { + describe('Inheritance', () => { + it('should extend MacOSComputerInterface', () => { const linuxInterface = new LinuxComputerInterface( testParams.ipAddress, testParams.username, diff --git a/libs/typescript/computer/tests/interface/macos.test.ts b/libs/typescript/computer/tests/interface/macos.test.ts index 72ee07ae..e655254f 100644 --- a/libs/typescript/computer/tests/interface/macos.test.ts +++ b/libs/typescript/computer/tests/interface/macos.test.ts @@ -1,15 +1,15 @@ -import { describe, expect, it, beforeEach, afterEach } from "vitest"; -import { MacOSComputerInterface } from "../../src/interface/macos.ts"; -import { WebSocketServer, WebSocket } from "ws"; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { WebSocket, WebSocketServer } from 'ws'; +import { MacOSComputerInterface } from '../../src/interface/macos.ts'; -describe("MacOSComputerInterface", () => { +describe('MacOSComputerInterface', () => { // Define test parameters const testParams = { - ipAddress: "localhost", - username: "testuser", - password: "testpass", + ipAddress: 'localhost', + username: 'testuser', + password: 'testpass', // apiKey: "test-api-key", No API Key for local testing - vmName: "test-vm", + vmName: 'test-vm', }; // WebSocket server mock @@ -37,28 +37,28 @@ describe("MacOSComputerInterface", () => { testParams.ipAddress = `localhost:${serverPort}`; // Handle WebSocket connections - wss.on("connection", (ws) => { + wss.on('connection', (ws) => { connectedClients.push(ws); // Handle incoming messages - ws.on("message", (data) => { + ws.on('message', (data) => { try { const message = JSON.parse(data.toString()); receivedMessages.push(message); // Send appropriate responses based on action switch (message.command) { - case "screenshot": + case 'screenshot': ws.send( JSON.stringify({ - image_data: Buffer.from("fake-screenshot-data").toString( - "base64" + image_data: Buffer.from('fake-screenshot-data').toString( + 'base64' ), success: true, }) ); break; - case "get_screen_size": + case 'get_screen_size': ws.send( JSON.stringify({ size: { width: 1920, height: 1080 }, @@ -66,7 +66,7 @@ describe("MacOSComputerInterface", () => { }) ); break; - case "get_cursor_position": + case 'get_cursor_position': ws.send( JSON.stringify({ position: { x: 100, y: 200 }, @@ -74,15 +74,15 @@ describe("MacOSComputerInterface", () => { }) ); break; - case "copy_to_clipboard": + case 'copy_to_clipboard': ws.send( JSON.stringify({ - content: "clipboard content", + content: 'clipboard content', success: true, }) ); break; - case "file_exists": + case 'file_exists': ws.send( JSON.stringify({ exists: true, @@ -90,7 +90,7 @@ describe("MacOSComputerInterface", () => { }) ); break; - case "directory_exists": + case 'directory_exists': ws.send( JSON.stringify({ exists: true, @@ -98,52 +98,52 @@ describe("MacOSComputerInterface", () => { }) ); break; - case "list_dir": + case 'list_dir': ws.send( JSON.stringify({ - files: ["file1.txt", "file2.txt"], + files: ['file1.txt', 'file2.txt'], success: true, }) ); break; - case "read_text": + case 'read_text': ws.send( JSON.stringify({ - content: "file content", + content: 'file content', success: true, }) ); break; - case "read_bytes": + case 'read_bytes': ws.send( JSON.stringify({ - content_b64: Buffer.from("binary content").toString("base64"), + content_b64: Buffer.from('binary content').toString('base64'), success: true, }) ); break; - case "run_command": + case 'run_command': ws.send( JSON.stringify({ - stdout: "command output", - stderr: "", + stdout: 'command output', + stderr: '', success: true, }) ); break; - case "get_accessibility_tree": + case 'get_accessibility_tree': ws.send( JSON.stringify({ - role: "window", - title: "Test Window", + role: 'window', + title: 'Test Window', bounds: { x: 0, y: 0, width: 1920, height: 1080 }, children: [], success: true, }) ); break; - case "to_screen_coordinates": - case "to_screenshot_coordinates": + case 'to_screen_coordinates': + case 'to_screenshot_coordinates': ws.send( JSON.stringify({ coordinates: [message.params?.x || 0, message.params?.y || 0], @@ -161,8 +161,8 @@ describe("MacOSComputerInterface", () => { } }); - ws.on("error", (error) => { - console.error("WebSocket error:", error); + ws.on('error', (error) => { + console.error('WebSocket error:', error); }); }); }); @@ -182,8 +182,8 @@ describe("MacOSComputerInterface", () => { }); }); - describe("Connection Management", () => { - it("should connect with proper authentication headers", async () => { + describe('Connection Management', () => { + it('should connect with proper authentication headers', async () => { const macosInterface = new MacOSComputerInterface( testParams.ipAddress, testParams.username, @@ -201,13 +201,13 @@ describe("MacOSComputerInterface", () => { await macosInterface.disconnect(); }); - it("should handle connection without API key", async () => { + it('should handle connection without API key', async () => { // Create a separate server that doesn't check auth const noAuthWss = new WebSocketServer({ port: 0 }); const noAuthPort = (noAuthWss.address() as { port: number }).port; - noAuthWss.on("connection", (ws) => { - ws.on("message", () => { + noAuthWss.on('connection', (ws) => { + ws.on('message', () => { ws.send(JSON.stringify({ success: true })); }); }); @@ -230,7 +230,7 @@ describe("MacOSComputerInterface", () => { }); }); - describe("Mouse Actions", () => { + describe('Mouse Actions', () => { let macosInterface: MacOSComputerInterface; beforeEach(async () => { @@ -250,40 +250,40 @@ describe("MacOSComputerInterface", () => { } }); - it("should send mouse_down command", async () => { - await macosInterface.mouseDown(100, 200, "left"); + it('should send mouse_down command', async () => { + await macosInterface.mouseDown(100, 200, 'left'); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "mouse_down", + command: 'mouse_down', params: { x: 100, y: 200, - button: "left", + button: 'left', }, }); }); - it("should send mouse_up command", async () => { - await macosInterface.mouseUp(100, 200, "right"); + it('should send mouse_up command', async () => { + await macosInterface.mouseUp(100, 200, 'right'); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "mouse_up", + command: 'mouse_up', params: { x: 100, y: 200, - button: "right", + button: 'right', }, }); }); - it("should send left_click command", async () => { + it('should send left_click command', async () => { await macosInterface.leftClick(150, 250); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "left_click", + command: 'left_click', params: { x: 150, y: 250, @@ -291,12 +291,12 @@ describe("MacOSComputerInterface", () => { }); }); - it("should send right_click command", async () => { + it('should send right_click command', async () => { await macosInterface.rightClick(200, 300); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "right_click", + command: 'right_click', params: { x: 200, y: 300, @@ -304,12 +304,12 @@ describe("MacOSComputerInterface", () => { }); }); - it("should send double_click command", async () => { + it('should send double_click command', async () => { await macosInterface.doubleClick(250, 350); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "double_click", + command: 'double_click', params: { x: 250, y: 350, @@ -317,12 +317,12 @@ describe("MacOSComputerInterface", () => { }); }); - it("should send move_cursor command", async () => { + it('should send move_cursor command', async () => { await macosInterface.moveCursor(300, 400); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "move_cursor", + command: 'move_cursor', params: { x: 300, y: 400, @@ -330,42 +330,42 @@ describe("MacOSComputerInterface", () => { }); }); - it("should send drag_to command", async () => { - await macosInterface.dragTo(400, 500, "left", 1.5); + it('should send drag_to command', async () => { + await macosInterface.dragTo(400, 500, 'left', 1.5); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "drag_to", + command: 'drag_to', params: { x: 400, y: 500, - button: "left", + button: 'left', duration: 1.5, }, }); }); - it("should send drag command with path", async () => { + it('should send drag command with path', async () => { const path: Array<[number, number]> = [ [100, 100], [200, 200], [300, 300], ]; - await macosInterface.drag(path, "middle", 2.0); + await macosInterface.drag(path, 'middle', 2.0); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "drag", + command: 'drag', params: { path: path, - button: "middle", + button: 'middle', duration: 2.0, }, }); }); }); - describe("Keyboard Actions", () => { + describe('Keyboard Actions', () => { let macosInterface: MacOSComputerInterface; beforeEach(async () => { @@ -385,68 +385,68 @@ describe("MacOSComputerInterface", () => { } }); - it("should send key_down command", async () => { - await macosInterface.keyDown("a"); + it('should send key_down command', async () => { + await macosInterface.keyDown('a'); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "key_down", + command: 'key_down', params: { - key: "a", + key: 'a', }, }); }); - it("should send key_up command", async () => { - await macosInterface.keyUp("b"); + it('should send key_up command', async () => { + await macosInterface.keyUp('b'); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "key_up", + command: 'key_up', params: { - key: "b", + key: 'b', }, }); }); - it("should send type_text command", async () => { - await macosInterface.typeText("Hello, World!"); + it('should send type_text command', async () => { + await macosInterface.typeText('Hello, World!'); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "type_text", + command: 'type_text', params: { - text: "Hello, World!", + text: 'Hello, World!', }, }); }); - it("should send press_key command", async () => { - await macosInterface.pressKey("enter"); + it('should send press_key command', async () => { + await macosInterface.pressKey('enter'); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "press_key", + command: 'press_key', params: { - key: "enter", + key: 'enter', }, }); }); - it("should send hotkey command", async () => { - await macosInterface.hotkey("cmd", "c"); + it('should send hotkey command', async () => { + await macosInterface.hotkey('cmd', 'c'); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "hotkey", + command: 'hotkey', params: { - keys: ["cmd", "c"], + keys: ['cmd', 'c'], }, }); }); }); - describe("Scrolling Actions", () => { + describe('Scrolling Actions', () => { let macosInterface: MacOSComputerInterface; beforeEach(async () => { @@ -466,12 +466,12 @@ describe("MacOSComputerInterface", () => { } }); - it("should send scroll command", async () => { + it('should send scroll command', async () => { await macosInterface.scroll(10, -5); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "scroll", + command: 'scroll', params: { x: 10, y: -5, @@ -479,24 +479,24 @@ describe("MacOSComputerInterface", () => { }); }); - it("should send scroll_down command", async () => { + it('should send scroll_down command', async () => { await macosInterface.scrollDown(3); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "scroll_down", + command: 'scroll_down', params: { clicks: 3, }, }); }); - it("should send scroll_up command", async () => { + it('should send scroll_up command', async () => { await macosInterface.scrollUp(2); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "scroll_up", + command: 'scroll_up', params: { clicks: 2, }, @@ -504,7 +504,7 @@ describe("MacOSComputerInterface", () => { }); }); - describe("Screen Actions", () => { + describe('Screen Actions', () => { let macosInterface: MacOSComputerInterface; beforeEach(async () => { @@ -524,45 +524,45 @@ describe("MacOSComputerInterface", () => { } }); - it("should get screenshot", async () => { + it('should get screenshot', async () => { const screenshot = await macosInterface.screenshot(); expect(screenshot).toBeInstanceOf(Buffer); - expect(screenshot.toString()).toBe("fake-screenshot-data"); + expect(screenshot.toString()).toBe('fake-screenshot-data'); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "screenshot", + command: 'screenshot', params: {}, }); }); - it("should get screen size", async () => { + it('should get screen size', async () => { const size = await macosInterface.getScreenSize(); expect(size).toEqual({ width: 1920, height: 1080 }); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "get_screen_size", + command: 'get_screen_size', params: {}, }); }); - it("should get cursor position", async () => { + it('should get cursor position', async () => { const position = await macosInterface.getCursorPosition(); expect(position).toEqual({ x: 100, y: 200 }); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "get_cursor_position", + command: 'get_cursor_position', params: {}, }); }); }); - describe("Clipboard Actions", () => { + describe('Clipboard Actions', () => { let macosInterface: MacOSComputerInterface; beforeEach(async () => { @@ -582,32 +582,32 @@ describe("MacOSComputerInterface", () => { } }); - it("should copy to clipboard", async () => { + it('should copy to clipboard', async () => { const text = await macosInterface.copyToClipboard(); - expect(text).toBe("clipboard content"); + expect(text).toBe('clipboard content'); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "copy_to_clipboard", + command: 'copy_to_clipboard', params: {}, }); }); - it("should set clipboard", async () => { - await macosInterface.setClipboard("new clipboard text"); + it('should set clipboard', async () => { + await macosInterface.setClipboard('new clipboard text'); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "set_clipboard", + command: 'set_clipboard', params: { - text: "new clipboard text", + text: 'new clipboard text', }, }); }); }); - describe("File System Actions", () => { + describe('File System Actions', () => { let macosInterface: MacOSComputerInterface; beforeEach(async () => { @@ -627,157 +627,157 @@ describe("MacOSComputerInterface", () => { } }); - it("should check file exists", async () => { - const exists = await macosInterface.fileExists("/path/to/file"); + it('should check file exists', async () => { + const exists = await macosInterface.fileExists('/path/to/file'); expect(exists).toBe(true); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "file_exists", + command: 'file_exists', params: { - path: "/path/to/file", + path: '/path/to/file', }, }); }); - it("should check directory exists", async () => { - const exists = await macosInterface.directoryExists("/path/to/dir"); + it('should check directory exists', async () => { + const exists = await macosInterface.directoryExists('/path/to/dir'); expect(exists).toBe(true); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "directory_exists", + command: 'directory_exists', params: { - path: "/path/to/dir", + path: '/path/to/dir', }, }); }); - it("should list directory", async () => { - const files = await macosInterface.listDir("/path/to/dir"); + it('should list directory', async () => { + const files = await macosInterface.listDir('/path/to/dir'); - expect(files).toEqual(["file1.txt", "file2.txt"]); + expect(files).toEqual(['file1.txt', 'file2.txt']); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "list_dir", + command: 'list_dir', params: { - path: "/path/to/dir", + path: '/path/to/dir', }, }); }); - it("should read text file", async () => { - const content = await macosInterface.readText("/path/to/file.txt"); + it('should read text file', async () => { + const content = await macosInterface.readText('/path/to/file.txt'); - expect(content).toBe("file content"); + expect(content).toBe('file content'); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "read_text", + command: 'read_text', params: { - path: "/path/to/file.txt", + path: '/path/to/file.txt', }, }); }); - it("should write text file", async () => { - await macosInterface.writeText("/path/to/file.txt", "new content"); + it('should write text file', async () => { + await macosInterface.writeText('/path/to/file.txt', 'new content'); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "write_text", + command: 'write_text', params: { - path: "/path/to/file.txt", - content: "new content", + path: '/path/to/file.txt', + content: 'new content', }, }); }); - it("should read binary file", async () => { - const content = await macosInterface.readBytes("/path/to/file.bin"); + it('should read binary file', async () => { + const content = await macosInterface.readBytes('/path/to/file.bin'); expect(content).toBeInstanceOf(Buffer); - expect(content.toString()).toBe("binary content"); + expect(content.toString()).toBe('binary content'); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "read_bytes", + command: 'read_bytes', params: { - path: "/path/to/file.bin", + path: '/path/to/file.bin', }, }); }); - it("should write binary file", async () => { - const buffer = Buffer.from("binary data"); - await macosInterface.writeBytes("/path/to/file.bin", buffer); + it('should write binary file', async () => { + const buffer = Buffer.from('binary data'); + await macosInterface.writeBytes('/path/to/file.bin', buffer); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "write_bytes", + command: 'write_bytes', params: { - path: "/path/to/file.bin", - content_b64: buffer.toString("base64"), + path: '/path/to/file.bin', + content_b64: buffer.toString('base64'), }, }); }); - it("should delete file", async () => { - await macosInterface.deleteFile("/path/to/file"); + it('should delete file', async () => { + await macosInterface.deleteFile('/path/to/file'); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "delete_file", + command: 'delete_file', params: { - path: "/path/to/file", + path: '/path/to/file', }, }); }); - it("should create directory", async () => { - await macosInterface.createDir("/path/to/new/dir"); + it('should create directory', async () => { + await macosInterface.createDir('/path/to/new/dir'); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "create_dir", + command: 'create_dir', params: { - path: "/path/to/new/dir", + path: '/path/to/new/dir', }, }); }); - it("should delete directory", async () => { - await macosInterface.deleteDir("/path/to/dir"); + it('should delete directory', async () => { + await macosInterface.deleteDir('/path/to/dir'); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "delete_dir", + command: 'delete_dir', params: { - path: "/path/to/dir", + path: '/path/to/dir', }, }); }); - it("should run command", async () => { - const [stdout, stderr] = await macosInterface.runCommand("ls -la"); + it('should run command', async () => { + const [stdout, stderr] = await macosInterface.runCommand('ls -la'); - expect(stdout).toBe("command output"); - expect(stderr).toBe(""); + expect(stdout).toBe('command output'); + expect(stderr).toBe(''); const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "run_command", + command: 'run_command', params: { - command: "ls -la", + command: 'ls -la', }, }); }); }); - describe("Accessibility Actions", () => { + describe('Accessibility Actions', () => { let macosInterface: MacOSComputerInterface; beforeEach(async () => { @@ -797,12 +797,12 @@ describe("MacOSComputerInterface", () => { } }); - it("should get accessibility tree", async () => { + it('should get accessibility tree', async () => { const tree = await macosInterface.getAccessibilityTree(); expect(tree).toEqual({ - role: "window", - title: "Test Window", + role: 'window', + title: 'Test Window', bounds: { x: 0, y: 0, width: 1920, height: 1080 }, children: [], success: true, @@ -810,12 +810,12 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "get_accessibility_tree", + command: 'get_accessibility_tree', params: {}, }); }); - it("should convert to screen coordinates", async () => { + it('should convert to screen coordinates', async () => { const [x, y] = await macosInterface.toScreenCoordinates(100, 200); expect(x).toBe(100); @@ -823,7 +823,7 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "to_screen_coordinates", + command: 'to_screen_coordinates', params: { x: 100, y: 200, @@ -831,7 +831,7 @@ describe("MacOSComputerInterface", () => { }); }); - it("should convert to screenshot coordinates", async () => { + it('should convert to screenshot coordinates', async () => { const [x, y] = await macosInterface.toScreenshotCoordinates(300, 400); expect(x).toBe(300); @@ -839,7 +839,7 @@ describe("MacOSComputerInterface", () => { const lastMessage = receivedMessages[receivedMessages.length - 1]; expect(lastMessage).toEqual({ - command: "to_screenshot_coordinates", + command: 'to_screenshot_coordinates', params: { x: 300, y: 400, @@ -848,11 +848,11 @@ describe("MacOSComputerInterface", () => { }); }); - describe("Error Handling", () => { - it("should handle WebSocket connection errors", async () => { + describe('Error Handling', () => { + it('should handle WebSocket connection errors', async () => { // Use a valid but unreachable IP to avoid DNS errors const macosInterface = new MacOSComputerInterface( - "localhost:9999", + 'localhost:9999', testParams.username, testParams.password, undefined, @@ -863,14 +863,14 @@ describe("MacOSComputerInterface", () => { await expect(macosInterface.connect()).rejects.toThrow(); }); - it("should handle command errors", async () => { + it('should handle command errors', async () => { // Create a server that returns errors const errorWss = new WebSocketServer({ port: 0 }); const errorPort = (errorWss.address() as { port: number }).port; - errorWss.on("connection", (ws) => { - ws.on("message", () => { - ws.send(JSON.stringify({ error: "Command failed", success: false })); + errorWss.on('connection', (ws) => { + ws.on('message', () => { + ws.send(JSON.stringify({ error: 'Command failed', success: false })); }); }); @@ -886,7 +886,7 @@ describe("MacOSComputerInterface", () => { // Command should throw error await expect(macosInterface.leftClick(100, 100)).rejects.toThrow( - "Command failed" + 'Command failed' ); await macosInterface.disconnect(); @@ -895,7 +895,7 @@ describe("MacOSComputerInterface", () => { }); }); - it("should handle disconnection gracefully", async () => { + it('should handle disconnection gracefully', async () => { const macosInterface = new MacOSComputerInterface( testParams.ipAddress, testParams.username, @@ -918,7 +918,7 @@ describe("MacOSComputerInterface", () => { await macosInterface.disconnect(); }); - it("should handle force close", async () => { + it('should handle force close', async () => { const macosInterface = new MacOSComputerInterface( testParams.ipAddress, testParams.username, diff --git a/libs/typescript/computer/tests/interface/windows.test.ts b/libs/typescript/computer/tests/interface/windows.test.ts index cf8fe9e9..68a7490d 100644 --- a/libs/typescript/computer/tests/interface/windows.test.ts +++ b/libs/typescript/computer/tests/interface/windows.test.ts @@ -1,18 +1,18 @@ -import { describe, expect, it } from "vitest"; -import { WindowsComputerInterface } from "../../src/interface/windows.ts"; -import { MacOSComputerInterface } from "../../src/interface/macos.ts"; +import { describe, expect, it } from 'vitest'; +import { MacOSComputerInterface } from '../../src/interface/macos.ts'; +import { WindowsComputerInterface } from '../../src/interface/windows.ts'; -describe("WindowsComputerInterface", () => { +describe('WindowsComputerInterface', () => { const testParams = { - ipAddress: "192.0.2.1", // TEST-NET-1 address (RFC 5737) - guaranteed not to be routable - username: "testuser", - password: "testpass", - apiKey: "test-api-key", - vmName: "test-vm", + ipAddress: '192.0.2.1', // TEST-NET-1 address (RFC 5737) - guaranteed not to be routable + username: 'testuser', + password: 'testpass', + apiKey: 'test-api-key', + vmName: 'test-vm', }; - describe("Inheritance", () => { - it("should extend MacOSComputerInterface", () => { + describe('Inheritance', () => { + it('should extend MacOSComputerInterface', () => { const windowsInterface = new WindowsComputerInterface( testParams.ipAddress, testParams.username, diff --git a/libs/typescript/computer/tests/setup.ts b/libs/typescript/computer/tests/setup.ts index 7dd1ebc7..9425b20f 100644 --- a/libs/typescript/computer/tests/setup.ts +++ b/libs/typescript/computer/tests/setup.ts @@ -1,4 +1,4 @@ -import { afterAll, afterEach, beforeAll } from "vitest"; +import { afterAll, afterEach, beforeAll } from 'vitest'; beforeAll(() => {}); diff --git a/libs/typescript/computer/tsdown.config.ts b/libs/typescript/computer/tsdown.config.ts index 19e6e5b9..b3c70ea9 100644 --- a/libs/typescript/computer/tsdown.config.ts +++ b/libs/typescript/computer/tsdown.config.ts @@ -1,10 +1,10 @@ -import { defineConfig } from "tsdown"; +import { defineConfig } from 'tsdown'; export default defineConfig([ { - entry: ["./src/index.ts"], - platform: "node", + entry: ['./src/index.ts'], + platform: 'node', dts: true, - external: ["child_process", "util"], + external: ['child_process', 'util'], }, ]); diff --git a/libs/typescript/computer/vitest.config.ts b/libs/typescript/computer/vitest.config.ts index 4f76f6cc..53ab43d4 100644 --- a/libs/typescript/computer/vitest.config.ts +++ b/libs/typescript/computer/vitest.config.ts @@ -1,9 +1,9 @@ -import { defineConfig } from "vitest/config"; +import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { - setupFiles: ["./tests/setup.ts"], - environment: "node", + setupFiles: ['./tests/setup.ts'], + environment: 'node', globals: true, }, }); diff --git a/libs/typescript/core/src/telemetry/clients/index.ts b/libs/typescript/core/src/telemetry/clients/index.ts index cbb1c307..450fcfbb 100644 --- a/libs/typescript/core/src/telemetry/clients/index.ts +++ b/libs/typescript/core/src/telemetry/clients/index.ts @@ -1 +1 @@ -export * from "./posthog"; +export * from './posthog'; diff --git a/libs/typescript/core/src/telemetry/clients/posthog.ts b/libs/typescript/core/src/telemetry/clients/posthog.ts index 18216c21..0ef9df85 100644 --- a/libs/typescript/core/src/telemetry/clients/posthog.ts +++ b/libs/typescript/core/src/telemetry/clients/posthog.ts @@ -2,12 +2,12 @@ * Telemetry client using PostHog for collecting anonymous usage data. */ +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import { pino } from 'pino'; import { PostHog } from 'posthog-node'; import { v4 as uuidv4 } from 'uuid'; -import * as fs from 'node:fs'; -import * as path from 'node:path'; -import * as os from 'node:os'; -import { pino } from 'pino'; const logger = pino({ name: 'core.telemetry' }); // Controls how frequently telemetry will be sent (percentage) diff --git a/libs/typescript/core/tests/telemetry.test.ts b/libs/typescript/core/tests/telemetry.test.ts index f8eb780b..4c4d47f6 100644 --- a/libs/typescript/core/tests/telemetry.test.ts +++ b/libs/typescript/core/tests/telemetry.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, beforeEach } from 'vitest'; +import { beforeEach, describe, expect, it } from 'vitest'; import { Telemetry } from '../src/'; describe('Telemetry', () => { diff --git a/libs/typescript/core/tsdown.config.ts b/libs/typescript/core/tsdown.config.ts index f30bbedd..21d62816 100644 --- a/libs/typescript/core/tsdown.config.ts +++ b/libs/typescript/core/tsdown.config.ts @@ -1,4 +1,4 @@ -import { defineConfig } from 'tsdown' +import { defineConfig } from 'tsdown'; export default defineConfig([ { @@ -6,4 +6,4 @@ export default defineConfig([ platform: 'neutral', dts: true, }, -]) +]); diff --git a/libs/typescript/core/vitest.config.ts b/libs/typescript/core/vitest.config.ts index abed6b21..94ede10e 100644 --- a/libs/typescript/core/vitest.config.ts +++ b/libs/typescript/core/vitest.config.ts @@ -1,3 +1,3 @@ -import { defineConfig } from 'vitest/config' +import { defineConfig } from 'vitest/config'; -export default defineConfig({}) +export default defineConfig({}); diff --git a/libs/typescript/package.json b/libs/typescript/package.json index f14aa59e..ff69852c 100644 --- a/libs/typescript/package.json +++ b/libs/typescript/package.json @@ -6,8 +6,8 @@ "author": "c/ua", "license": "MIT", "scripts": { - "lint": "biome lint .", - "lint:fix": "biome lint --fix ." + "lint": "biome check", + "lint:fix": "biome check --fix" }, "packageManager": "pnpm@10.6.5", "devDependencies": { From 8d56233b3ddf58c1e2cbc8768afa89134ac1794b Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Mon, 23 Jun 2025 16:20:30 -0700 Subject: [PATCH 101/141] Finish setting up pnpm workspace --- libs/typescript/pnpm-workspace.yaml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 libs/typescript/pnpm-workspace.yaml diff --git a/libs/typescript/pnpm-workspace.yaml b/libs/typescript/pnpm-workspace.yaml new file mode 100644 index 00000000..28d8bfd5 --- /dev/null +++ b/libs/typescript/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - "computer/*" + - "core/*" From 35434934cc0f3fe6685f2097b931dd49276374e8 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 24 Jun 2025 08:55:14 -0400 Subject: [PATCH 102/141] added shell tests --- tests/shell_bash.py | 86 ++++++++++++++++++++++++++++++++++++++++++++ tests/shell_cmd.py | 87 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 tests/shell_bash.py create mode 100644 tests/shell_cmd.py diff --git a/tests/shell_bash.py b/tests/shell_bash.py new file mode 100644 index 00000000..af34ff0e --- /dev/null +++ b/tests/shell_bash.py @@ -0,0 +1,86 @@ +""" +Shell Command Tests (Bash) +Tests for the run_command method of the Computer interface using bash commands. +Required environment variables: +- CUA_API_KEY: API key for C/ua cloud provider +- CUA_CONTAINER_NAME: Name of the container to use +""" + +import os +import asyncio +import pytest +from pathlib import Path +import sys +import traceback + +# Load environment variables from .env file +project_root = Path(__file__).parent.parent +env_file = project_root / ".env" +print(f"Loading environment from: {env_file}") +from dotenv import load_dotenv + +load_dotenv(env_file) + +# Add paths to sys.path if needed +pythonpath = os.environ.get("PYTHONPATH", "") +for path in pythonpath.split(":"): + if path and path not in sys.path: + sys.path.insert(0, path) # Insert at beginning to prioritize + print(f"Added to sys.path: {path}") + +from computer import Computer, VMProviderType + +@pytest.fixture(scope="session") +async def computer(): + """Shared Computer instance for all test cases.""" + # Create a remote Linux computer with C/ua + computer = Computer( + os_type="linux", + api_key=os.getenv("CUA_API_KEY"), + name=str(os.getenv("CUA_CONTAINER_NAME")), + provider_type=VMProviderType.CLOUD, + ) + + try: + await computer.run() + yield computer + finally: + await computer.disconnect() + + +# Sample test cases +@pytest.mark.asyncio(loop_scope="session") +async def test_bash_echo_command(computer): + """Test basic echo command with bash.""" + result = await computer.interface.run_command("echo 'Hello World'") + + assert result.stdout.strip() == "Hello World" + assert result.stderr == "" + assert result.returncode == 0 + + +@pytest.mark.asyncio(loop_scope="session") +async def test_bash_ls_command(computer): + """Test ls command to list directory contents.""" + result = await computer.interface.run_command("ls -la /tmp") + + assert result.returncode == 0 + assert result.stderr == "" + assert "total" in result.stdout # ls -la typically starts with "total" + assert "." in result.stdout # Current directory entry + assert ".." in result.stdout # Parent directory entry + + +@pytest.mark.asyncio(loop_scope="session") +async def test_bash_command_with_error(computer): + """Test command that produces an error.""" + result = await computer.interface.run_command("ls /nonexistent_directory_12345") + + assert result.returncode != 0 + assert result.stdout == "" + assert "No such file or directory" in result.stderr or "cannot access" in result.stderr + + +if __name__ == "__main__": + # Run tests directly + pytest.main([__file__, "-v"]) diff --git a/tests/shell_cmd.py b/tests/shell_cmd.py new file mode 100644 index 00000000..a210e453 --- /dev/null +++ b/tests/shell_cmd.py @@ -0,0 +1,87 @@ +""" +Shell Command Tests (CMD) +Tests for the run_command method of the Computer interface using cmd.exe commands. +Required environment variables: +- CUA_API_KEY: API key for C/ua cloud provider +- CUA_CONTAINER_NAME: Name of the container to use +""" + +import os +import asyncio +import pytest +from pathlib import Path +import sys +import traceback + +# Load environment variables from .env file +project_root = Path(__file__).parent.parent +env_file = project_root / ".env" +print(f"Loading environment from: {env_file}") +from dotenv import load_dotenv + +load_dotenv(env_file) + +# Add paths to sys.path if needed +pythonpath = os.environ.get("PYTHONPATH", "") +for path in pythonpath.split(":"): + if path and path not in sys.path: + sys.path.insert(0, path) # Insert at beginning to prioritize + print(f"Added to sys.path: {path}") + +from computer import Computer, VMProviderType + +@pytest.fixture(scope="session") +async def computer(): + """Shared Computer instance for all test cases.""" + # Create a remote Windows computer with C/ua + computer = Computer( + os_type="windows", + api_key=os.getenv("CUA_API_KEY"), + name=str(os.getenv("CUA_CONTAINER_NAME")), + provider_type=VMProviderType.CLOUD, + ) + + try: + await computer.run() + yield computer + finally: + await computer.disconnect() + + +# Sample test cases +@pytest.mark.asyncio(loop_scope="session") +async def test_cmd_echo_command(computer): + """Test basic echo command with cmd.exe.""" + result = await computer.interface.run_command("echo Hello World") + + assert result.stdout.strip() == "Hello World" + assert result.stderr == "" + assert result.returncode == 0 + + +@pytest.mark.asyncio(loop_scope="session") +async def test_cmd_dir_command(computer): + """Test dir command to list directory contents.""" + result = await computer.interface.run_command("dir C:\\") + + assert result.returncode == 0 + assert result.stderr == "" + assert "Directory of C:\\" in result.stdout + assert "bytes" in result.stdout.lower() # dir typically shows file sizes + + +@pytest.mark.asyncio(loop_scope="session") +async def test_cmd_command_with_error(computer): + """Test command that produces an error.""" + result = await computer.interface.run_command("dir C:\\nonexistent_directory_12345") + + assert result.returncode != 0 + assert result.stdout == "" + assert ("File Not Found" in result.stderr or + "cannot find the path" in result.stderr or + "The system cannot find" in result.stderr) + + +if __name__ == "__main__": + # Run tests directly + pytest.main([__file__, "-v"]) From 9774bcd3a7a812d583ee996beda37f9128795096 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Wed, 25 Jun 2025 12:12:30 -0400 Subject: [PATCH 103/141] Bump version for breaking change --- .../agent/providers/anthropic/tools/bash.py | 4 ++-- .../agent/providers/anthropic/tools/edit.py | 18 +++++++++--------- libs/agent/pyproject.toml | 2 +- libs/computer/pyproject.toml | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/libs/agent/agent/providers/anthropic/tools/bash.py b/libs/agent/agent/providers/anthropic/tools/bash.py index babbacfd..479e1127 100644 --- a/libs/agent/agent/providers/anthropic/tools/bash.py +++ b/libs/agent/agent/providers/anthropic/tools/bash.py @@ -50,8 +50,8 @@ class BashTool(BaseBashTool, BaseAnthropicTool): try: async with asyncio.timeout(self._timeout): - stdout, stderr = await self.computer.interface.run_command(command) - return CLIResult(output=stdout or "", error=stderr or "") + result = await self.computer.interface.run_command(command) + return CLIResult(output=result.stdout or "", error=result.stderr or "") except asyncio.TimeoutError as e: raise ToolError(f"Command timed out after {self._timeout} seconds") from e except Exception as e: diff --git a/libs/agent/agent/providers/anthropic/tools/edit.py b/libs/agent/agent/providers/anthropic/tools/edit.py index e4da1f85..1114b586 100644 --- a/libs/agent/agent/providers/anthropic/tools/edit.py +++ b/libs/agent/agent/providers/anthropic/tools/edit.py @@ -95,13 +95,13 @@ class EditTool(BaseEditTool, BaseAnthropicTool): result = await self.computer.interface.run_command( f'[ -e "{str(path)}" ] && echo "exists" || echo "not exists"' ) - exists = result[0].strip() == "exists" + exists = result.stdout.strip() == "exists" if exists: result = await self.computer.interface.run_command( f'[ -d "{str(path)}" ] && echo "dir" || echo "file"' ) - is_dir = result[0].strip() == "dir" + is_dir = result.stdout.strip() == "dir" else: is_dir = False @@ -126,7 +126,7 @@ class EditTool(BaseEditTool, BaseAnthropicTool): result = await self.computer.interface.run_command( f'[ -d "{str(path)}" ] && echo "dir" || echo "file"' ) - is_dir = result[0].strip() == "dir" + is_dir = result.stdout.strip() == "dir" if is_dir: if view_range: @@ -136,7 +136,7 @@ class EditTool(BaseEditTool, BaseAnthropicTool): # List directory contents using ls result = await self.computer.interface.run_command(f'ls -la "{str(path)}"') - contents = result[0] + contents = result.stdout if contents: stdout = f"Here's the files and directories in {path}:\n{contents}\n" else: @@ -272,9 +272,9 @@ class EditTool(BaseEditTool, BaseAnthropicTool): """Read the content of a file using cat command.""" try: result = await self.computer.interface.run_command(f'cat "{str(path)}"') - if result[1]: # If there's stderr output - raise ToolError(f"Error reading file: {result[1]}") - return result[0] + if result.stderr: # If there's stderr output + raise ToolError(f"Error reading file: {result.stderr}") + return result.stdout except Exception as e: raise ToolError(f"Failed to read {path}: {str(e)}") @@ -291,8 +291,8 @@ class EditTool(BaseEditTool, BaseAnthropicTool): {content} EOFCUA""" result = await self.computer.interface.run_command(cmd) - if result[1]: # If there's stderr output - raise ToolError(f"Error writing file: {result[1]}") + if result.stderr: # If there's stderr output + raise ToolError(f"Error writing file: {result.stderr}") except Exception as e: raise ToolError(f"Failed to write to {path}: {str(e)}") diff --git a/libs/agent/pyproject.toml b/libs/agent/pyproject.toml index f078a5c9..279a5b9f 100644 --- a/libs/agent/pyproject.toml +++ b/libs/agent/pyproject.toml @@ -19,7 +19,7 @@ dependencies = [ "pydantic>=2.6.4,<3.0.0", "rich>=13.7.1,<14.0.0", "python-dotenv>=1.0.1,<2.0.0", - "cua-computer>=0.2.0,<0.3.0", + "cua-computer>=0.3.0,<0.4.0", "cua-core>=0.1.0,<0.2.0", "certifi>=2024.2.2" ] diff --git a/libs/computer/pyproject.toml b/libs/computer/pyproject.toml index 04bd2dfb..de0ed190 100644 --- a/libs/computer/pyproject.toml +++ b/libs/computer/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "pdm.backend" [project] name = "cua-computer" -version = "0.2.0" +version = "0.3.0" description = "Computer-Use Interface (CUI) framework powering Cua" readme = "README.md" authors = [ From 0f917e2966b815888bf32ffd91fed9ec09e2ea56 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 25 Jun 2025 11:52:01 -0700 Subject: [PATCH 104/141] Fix some issues with the pnpm workspace, add readme & scripts --- libs/typescript/README.md | 119 ++ libs/typescript/computer/pnpm-lock.yaml | 2335 ----------------------- libs/typescript/core/pnpm-lock.yaml | 1863 ------------------ libs/typescript/package.json | 12 +- libs/typescript/pnpm-lock.yaml | 1803 +++++++++++++++++ libs/typescript/pnpm-workspace.yaml | 4 +- 6 files changed, 1934 insertions(+), 4202 deletions(-) create mode 100644 libs/typescript/README.md delete mode 100644 libs/typescript/computer/pnpm-lock.yaml delete mode 100644 libs/typescript/core/pnpm-lock.yaml diff --git a/libs/typescript/README.md b/libs/typescript/README.md new file mode 100644 index 00000000..7bf62a45 --- /dev/null +++ b/libs/typescript/README.md @@ -0,0 +1,119 @@ +# C/UA TypeScript Libraries + +This repository contains TypeScript implementations of the C/UA libraries: + +- `@cua/core`: Core functionality including telemetry and logging +- `@cua/computer`: Computer interaction SDK for VM management and control + +## Project Structure + +```text +libs/typescript/ +├── computer/ # Computer SDK package +├── core/ # Core functionality package +├── package.json # Root package configuration +└── pnpm-workspace.yaml # Workspace configuration +``` + +## Prerequisites + +- [Node.js](https://nodejs.org/) (v18 or later) +- [pnpm](https://pnpm.io/) (v10 or later) + +## Setup and Installation + +1. Install dependencies for all packages: + +```bash +pnpm install +``` + +1. Build all packages: + +```bash +pnpm build:all +``` + +## Development Workflow + +### Building Packages + +Build all packages in the correct dependency order: + +```bash +pnpm build:all +``` + +Build specific packages: + +```bash +# Build core package +pnpm --filter @cua/core build + +# Build computer package +pnpm --filter @cua/computer build +``` + +### Running Tests + +Run tests for all packages: + +```bash +pnpm test:all +``` + +Run tests for specific packages: + +```bash +# Test core package +pnpm --filter @cua/core test + +# Test computer package +pnpm --filter @cua/computer test +``` + +### Linting + +Lint all packages: + +```bash +pnpm lint:all +``` + +Fix linting issues: + +```bash +pnpm lint:fix:all +``` + +## Package Details + +### @cua/core + +Core functionality for C/UA libraries including: + +- Telemetry with PostHog integration +- Common utilities and types + +### @cua/computer + +Computer interaction SDK for managing and controlling virtual machines: + +- VM provider system (Cloud) +- Interface system for OS-specific interactions +- Screenshot, keyboard, and mouse control +- Command execution + +## Publishing + +Prepare packages for publishing: + +```bash +pnpm -r build +``` + +Publish packages: + +```bash +pnpm -r publish +``` diff --git a/libs/typescript/computer/pnpm-lock.yaml b/libs/typescript/computer/pnpm-lock.yaml deleted file mode 100644 index 10c0a8b1..00000000 --- a/libs/typescript/computer/pnpm-lock.yaml +++ /dev/null @@ -1,2335 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@cua/core': - specifier: link:../core - version: link:../core - pino: - specifier: ^9.7.0 - version: 9.7.0 - ws: - specifier: ^8.18.0 - version: 8.18.2 - devDependencies: - '@biomejs/biome': - specifier: ^1.9.4 - version: 1.9.4 - '@types/node': - specifier: ^22.15.17 - version: 22.15.31 - '@types/ws': - specifier: ^8.18.1 - version: 8.18.1 - bumpp: - specifier: ^10.1.0 - version: 10.1.1 - happy-dom: - specifier: ^17.4.7 - version: 17.6.3 - tsdown: - specifier: ^0.11.9 - version: 0.11.13(typescript@5.8.3) - tsx: - specifier: ^4.19.4 - version: 4.20.2 - typescript: - specifier: ^5.8.3 - version: 5.8.3 - vitest: - specifier: ^3.1.3 - version: 3.2.3(@types/debug@4.1.12)(@types/node@22.15.31)(happy-dom@17.6.3)(jiti@2.4.2)(msw@2.10.2(@types/node@22.15.31)(typescript@5.8.3))(tsx@4.20.2)(yaml@2.8.0) - -packages: - - '@babel/generator@7.27.5': - resolution: {integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.27.1': - resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.27.5': - resolution: {integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/types@7.27.6': - resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==} - engines: {node: '>=6.9.0'} - - '@biomejs/biome@1.9.4': - resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} - engines: {node: '>=14.21.3'} - hasBin: true - - '@biomejs/cli-darwin-arm64@1.9.4': - resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} - engines: {node: '>=14.21.3'} - cpu: [arm64] - os: [darwin] - - '@biomejs/cli-darwin-x64@1.9.4': - resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==} - engines: {node: '>=14.21.3'} - cpu: [x64] - os: [darwin] - - '@biomejs/cli-linux-arm64-musl@1.9.4': - resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==} - engines: {node: '>=14.21.3'} - cpu: [arm64] - os: [linux] - - '@biomejs/cli-linux-arm64@1.9.4': - resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==} - engines: {node: '>=14.21.3'} - cpu: [arm64] - os: [linux] - - '@biomejs/cli-linux-x64-musl@1.9.4': - resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==} - engines: {node: '>=14.21.3'} - cpu: [x64] - os: [linux] - - '@biomejs/cli-linux-x64@1.9.4': - resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==} - engines: {node: '>=14.21.3'} - cpu: [x64] - os: [linux] - - '@biomejs/cli-win32-arm64@1.9.4': - resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==} - engines: {node: '>=14.21.3'} - cpu: [arm64] - os: [win32] - - '@biomejs/cli-win32-x64@1.9.4': - resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==} - engines: {node: '>=14.21.3'} - cpu: [x64] - os: [win32] - - '@bundled-es-modules/cookie@2.0.1': - resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==} - - '@bundled-es-modules/statuses@1.0.1': - resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} - - '@bundled-es-modules/tough-cookie@0.1.6': - resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==} - - '@emnapi/core@1.4.3': - resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} - - '@emnapi/runtime@1.4.3': - resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} - - '@emnapi/wasi-threads@1.0.2': - resolution: {integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==} - - '@esbuild/aix-ppc64@0.25.5': - resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.25.5': - resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.25.5': - resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.25.5': - resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.25.5': - resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.25.5': - resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.25.5': - resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.25.5': - resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.25.5': - resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.25.5': - resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.25.5': - resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.25.5': - resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.25.5': - resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.25.5': - resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.25.5': - resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.25.5': - resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.25.5': - resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.25.5': - resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.25.5': - resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.25.5': - resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.25.5': - resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/sunos-x64@0.25.5': - resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.25.5': - resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.25.5': - resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.25.5': - resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@inquirer/confirm@5.1.12': - resolution: {integrity: sha512-dpq+ielV9/bqgXRUbNH//KsY6WEw9DrGPmipkpmgC1Y46cwuBTNx7PXFWTjc3MQ+urcc0QxoVHcMI0FW4Ok0hg==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/core@10.1.13': - resolution: {integrity: sha512-1viSxebkYN2nJULlzCxES6G9/stgHSepZ9LqqfdIGPHj5OHhiBUXVS0a6R0bEC2A+VL4D9w6QB66ebCr6HGllA==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@inquirer/figures@1.0.12': - resolution: {integrity: sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==} - engines: {node: '>=18'} - - '@inquirer/type@3.0.7': - resolution: {integrity: sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==} - engines: {node: '>=18'} - peerDependencies: - '@types/node': '>=18' - peerDependenciesMeta: - '@types/node': - optional: true - - '@jridgewell/gen-mapping@0.3.8': - resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} - engines: {node: '>=6.0.0'} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - - '@mswjs/interceptors@0.39.2': - resolution: {integrity: sha512-RuzCup9Ct91Y7V79xwCb146RaBRHZ7NBbrIUySumd1rpKqHL5OonaqrGIbug5hNwP/fRyxFMA6ISgw4FTtYFYg==} - engines: {node: '>=18'} - - '@napi-rs/wasm-runtime@0.2.11': - resolution: {integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==} - - '@open-draft/deferred-promise@2.2.0': - resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} - - '@open-draft/logger@0.3.0': - resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} - - '@open-draft/until@2.1.0': - resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - - '@oxc-project/types@0.70.0': - resolution: {integrity: sha512-ngyLUpUjO3dpqygSRQDx7nMx8+BmXbWOU4oIwTJFV2MVIDG7knIZwgdwXlQWLg3C3oxg1lS7ppMtPKqKFb7wzw==} - - '@quansync/fs@0.1.3': - resolution: {integrity: sha512-G0OnZbMWEs5LhDyqy2UL17vGhSVHkQIfVojMtEWVenvj0V5S84VBgy86kJIuNsGDp2p7sTKlpSIpBUWdC35OKg==} - engines: {node: '>=20.0.0'} - - '@rolldown/binding-darwin-arm64@1.0.0-beta.9': - resolution: {integrity: sha512-geUG/FUpm+membLC0NQBb39vVyOfguYZ2oyXc7emr6UjH6TeEECT4b0CPZXKFnELareTiU/Jfl70/eEgNxyQeA==} - cpu: [arm64] - os: [darwin] - - '@rolldown/binding-darwin-x64@1.0.0-beta.9': - resolution: {integrity: sha512-7wPXDwcOtv2I+pWTL2UNpNAxMAGukgBT90Jz4DCfwaYdGvQncF7J0S7IWrRVsRFhBavxM+65RcueE3VXw5UIbg==} - cpu: [x64] - os: [darwin] - - '@rolldown/binding-freebsd-x64@1.0.0-beta.9': - resolution: {integrity: sha512-agO5mONTNKVrcIt4SRxw5Ni0FOVV3gaH8dIiNp1A4JeU91b9kw7x+JRuNJAQuM2X3pYqVvA6qh13UTNOsaqM/Q==} - cpu: [x64] - os: [freebsd] - - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9': - resolution: {integrity: sha512-dDNDV9p/8WYDriS9HCcbH6y6+JP38o3enj/pMkdkmkxEnZ0ZoHIfQ9RGYWeRYU56NKBCrya4qZBJx49Jk9LRug==} - cpu: [arm] - os: [linux] - - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9': - resolution: {integrity: sha512-kZKegmHG1ZvfsFIwYU6DeFSxSIcIliXzeznsJHUo9D9/dlVSDi/PUvsRKcuJkQjZoejM6pk8MHN/UfgGdIhPHw==} - cpu: [arm64] - os: [linux] - - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.9': - resolution: {integrity: sha512-f+VL8mO31pyMJiJPr2aA1ryYONkP2UqgbwK7fKtKHZIeDd/AoUGn3+ujPqDhuy2NxgcJ5H8NaSvDpG1tJMHh+g==} - cpu: [arm64] - os: [linux] - - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.9': - resolution: {integrity: sha512-GiUEZ0WPjX5LouDoC3O8aJa4h6BLCpIvaAboNw5JoRour/3dC6rbtZZ/B5FC3/ySsN3/dFOhAH97ylQxoZJi7A==} - cpu: [x64] - os: [linux] - - '@rolldown/binding-linux-x64-musl@1.0.0-beta.9': - resolution: {integrity: sha512-AMb0dicw+QHh6RxvWo4BRcuTMgS0cwUejJRMpSyIcHYnKTbj6nUW4HbWNQuDfZiF27l6F5gEwBS+YLUdVzL9vg==} - cpu: [x64] - os: [linux] - - '@rolldown/binding-wasm32-wasi@1.0.0-beta.9': - resolution: {integrity: sha512-+pdaiTx7L8bWKvsAuCE0HAxP1ze1WOLoWGCawcrZbMSY10dMh2i82lJiH6tXGXbfYYwsNWhWE2NyG4peFZvRfQ==} - engines: {node: '>=14.21.3'} - cpu: [wasm32] - - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9': - resolution: {integrity: sha512-A7kN248viWvb8eZMzQu024TBKGoyoVYBsDG2DtoP8u2pzwoh5yDqUL291u01o4f8uzpUHq8mfwQJmcGChFu8KQ==} - cpu: [arm64] - os: [win32] - - '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9': - resolution: {integrity: sha512-DzKN7iEYjAP8AK8F2G2aCej3fk43Y/EQrVrR3gF0XREes56chjQ7bXIhw819jv74BbxGdnpPcslhet/cgt7WRA==} - cpu: [ia32] - os: [win32] - - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.9': - resolution: {integrity: sha512-GMWgTvvbZ8TfBsAiJpoz4SRq3IN3aUMn0rYm8q4I8dcEk4J1uISyfb6ZMzvqW+cvScTWVKWZNqnrmYOKLLUt4w==} - cpu: [x64] - os: [win32] - - '@rolldown/pluginutils@1.0.0-beta.9': - resolution: {integrity: sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==} - - '@rollup/rollup-android-arm-eabi@4.43.0': - resolution: {integrity: sha512-Krjy9awJl6rKbruhQDgivNbD1WuLb8xAclM4IR4cN5pHGAs2oIMMQJEiC3IC/9TZJ+QZkmZhlMO/6MBGxPidpw==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.43.0': - resolution: {integrity: sha512-ss4YJwRt5I63454Rpj+mXCXicakdFmKnUNxr1dLK+5rv5FJgAxnN7s31a5VchRYxCFWdmnDWKd0wbAdTr0J5EA==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.43.0': - resolution: {integrity: sha512-eKoL8ykZ7zz8MjgBenEF2OoTNFAPFz1/lyJ5UmmFSz5jW+7XbH1+MAgCVHy72aG59rbuQLcJeiMrP8qP5d/N0A==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.43.0': - resolution: {integrity: sha512-SYwXJgaBYW33Wi/q4ubN+ldWC4DzQY62S4Ll2dgfr/dbPoF50dlQwEaEHSKrQdSjC6oIe1WgzosoaNoHCdNuMg==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.43.0': - resolution: {integrity: sha512-SV+U5sSo0yujrjzBF7/YidieK2iF6E7MdF6EbYxNz94lA+R0wKl3SiixGyG/9Klab6uNBIqsN7j4Y/Fya7wAjQ==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.43.0': - resolution: {integrity: sha512-J7uCsiV13L/VOeHJBo5SjasKiGxJ0g+nQTrBkAsmQBIdil3KhPnSE9GnRon4ejX1XDdsmK/l30IYLiAaQEO0Cg==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.43.0': - resolution: {integrity: sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.43.0': - resolution: {integrity: sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.43.0': - resolution: {integrity: sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-musl@4.43.0': - resolution: {integrity: sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-loongarch64-gnu@4.43.0': - resolution: {integrity: sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-powerpc64le-gnu@4.43.0': - resolution: {integrity: sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.43.0': - resolution: {integrity: sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-riscv64-musl@4.43.0': - resolution: {integrity: sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.43.0': - resolution: {integrity: sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==} - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.43.0': - resolution: {integrity: sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-musl@4.43.0': - resolution: {integrity: sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-win32-arm64-msvc@4.43.0': - resolution: {integrity: sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.43.0': - resolution: {integrity: sha512-fYCTEyzf8d+7diCw8b+asvWDCLMjsCEA8alvtAutqJOJp/wL5hs1rWSqJ1vkjgW0L2NB4bsYJrpKkiIPRR9dvw==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.43.0': - resolution: {integrity: sha512-SnGhLiE5rlK0ofq8kzuDkM0g7FN1s5VYY+YSMTibP7CqShxCQvqtNxTARS4xX4PFJfHjG0ZQYX9iGzI3FQh5Aw==} - cpu: [x64] - os: [win32] - - '@tybys/wasm-util@0.9.0': - resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} - - '@types/chai@5.2.2': - resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} - - '@types/cookie@0.6.0': - resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - - '@types/debug@4.1.12': - resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} - - '@types/deep-eql@4.0.2': - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - - '@types/estree@1.0.7': - resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} - - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - - '@types/ms@2.1.0': - resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - - '@types/node@22.15.31': - resolution: {integrity: sha512-jnVe5ULKl6tijxUhvQeNbQG/84fHfg+yMak02cT8QVhBx/F05rAVxCGBYYTh2EKz22D6JF5ktXuNwdx7b9iEGw==} - - '@types/statuses@2.0.6': - resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==} - - '@types/tough-cookie@4.0.5': - resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} - - '@types/ws@8.18.1': - resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - - '@vitest/expect@3.2.3': - resolution: {integrity: sha512-W2RH2TPWVHA1o7UmaFKISPvdicFJH+mjykctJFoAkUw+SPTJTGjUNdKscFBrqM7IPnCVu6zihtKYa7TkZS1dkQ==} - - '@vitest/mocker@3.2.3': - resolution: {integrity: sha512-cP6fIun+Zx8he4rbWvi+Oya6goKQDZK+Yq4hhlggwQBbrlOQ4qtZ+G4nxB6ZnzI9lyIb+JnvyiJnPC2AGbKSPA==} - peerDependencies: - msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - - '@vitest/pretty-format@3.2.3': - resolution: {integrity: sha512-yFglXGkr9hW/yEXngO+IKMhP0jxyFw2/qys/CK4fFUZnSltD+MU7dVYGrH8rvPcK/O6feXQA+EU33gjaBBbAng==} - - '@vitest/runner@3.2.3': - resolution: {integrity: sha512-83HWYisT3IpMaU9LN+VN+/nLHVBCSIUKJzGxC5RWUOsK1h3USg7ojL+UXQR3b4o4UBIWCYdD2fxuzM7PQQ1u8w==} - - '@vitest/snapshot@3.2.3': - resolution: {integrity: sha512-9gIVWx2+tysDqUmmM1L0hwadyumqssOL1r8KJipwLx5JVYyxvVRfxvMq7DaWbZZsCqZnu/dZedaZQh4iYTtneA==} - - '@vitest/spy@3.2.3': - resolution: {integrity: sha512-JHu9Wl+7bf6FEejTCREy+DmgWe+rQKbK+y32C/k5f4TBIAlijhJbRBIRIOCEpVevgRsCQR2iHRUH2/qKVM/plw==} - - '@vitest/utils@3.2.3': - resolution: {integrity: sha512-4zFBCU5Pf+4Z6v+rwnZ1HU1yzOKKvDkMXZrymE2PBlbjKJRlrOxbvpfPSvJTGRIwGoahaOGvp+kbCoxifhzJ1Q==} - - ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansis@4.1.0: - resolution: {integrity: sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==} - engines: {node: '>=14'} - - args-tokenizer@0.3.0: - resolution: {integrity: sha512-xXAd7G2Mll5W8uo37GETpQ2VrE84M181Z7ugHFGQnJZ50M2mbOv0osSZ9VsSgPfJQ+LVG0prSi0th+ELMsno7Q==} - - assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - - ast-kit@2.1.0: - resolution: {integrity: sha512-ROM2LlXbZBZVk97crfw8PGDOBzzsJvN2uJCmwswvPUNyfH14eg90mSN3xNqsri1JS1G9cz0VzeDUhxJkTrr4Ew==} - engines: {node: '>=20.18.0'} - - atomic-sleep@1.0.0: - resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} - engines: {node: '>=8.0.0'} - - birpc@2.3.0: - resolution: {integrity: sha512-ijbtkn/F3Pvzb6jHypHRyve2QApOCZDR25D/VnkY2G/lBNcXCTsnsCxgY4k4PkVB7zfwzYbY3O9Lcqe3xufS5g==} - - bumpp@10.1.1: - resolution: {integrity: sha512-69ejE1J5O5qDN3oRu2jRas1nQmi5zEYepjzbYPpi1znuDnp+zZ9Yezsf/nYauWeoMNALQ5toniNGET05Txj2cQ==} - engines: {node: '>=18'} - hasBin: true - - c12@3.0.4: - resolution: {integrity: sha512-t5FaZTYbbCtvxuZq9xxIruYydrAGsJ+8UdP0pZzMiK2xl/gNiSOy0OxhLzHUEEb0m1QXYqfzfvyIFEmz/g9lqg==} - peerDependencies: - magicast: ^0.3.5 - peerDependenciesMeta: - magicast: - optional: true - - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - - chai@5.2.0: - resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} - engines: {node: '>=12'} - - check-error@2.1.1: - resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} - engines: {node: '>= 16'} - - chokidar@4.0.3: - resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} - engines: {node: '>= 14.16.0'} - - citty@0.1.6: - resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} - - cli-width@4.1.0: - resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} - engines: {node: '>= 12'} - - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - confbox@0.2.2: - resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} - - consola@3.4.2: - resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} - engines: {node: ^14.18.0 || >=16.10.0} - - cookie@0.7.2: - resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} - engines: {node: '>= 0.6'} - - debug@4.4.1: - resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - deep-eql@5.0.2: - resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} - engines: {node: '>=6'} - - defu@6.1.4: - resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} - - destr@2.0.5: - resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} - - diff@8.0.2: - resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} - engines: {node: '>=0.3.1'} - - dotenv@16.5.0: - resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} - engines: {node: '>=12'} - - dts-resolver@2.1.1: - resolution: {integrity: sha512-3BiGFhB6mj5Kv+W2vdJseQUYW+SKVzAFJL6YNP6ursbrwy1fXHRotfHi3xLNxe4wZl/K8qbAFeCDjZLjzqxxRw==} - engines: {node: '>=20.18.0'} - peerDependencies: - oxc-resolver: '>=11.0.0' - peerDependenciesMeta: - oxc-resolver: - optional: true - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - empathic@1.1.0: - resolution: {integrity: sha512-rsPft6CK3eHtrlp9Y5ALBb+hfK+DWnA4WFebbazxjWyx8vSm3rZeoM3z9irsjcqO3PYRzlfv27XIB4tz2DV7RA==} - engines: {node: '>=14'} - - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - - esbuild@0.25.5: - resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} - engines: {node: '>=18'} - hasBin: true - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - - expect-type@1.2.1: - resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} - engines: {node: '>=12.0.0'} - - exsolve@1.0.5: - resolution: {integrity: sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==} - - fast-redact@3.5.0: - resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} - engines: {node: '>=6'} - - fdir@6.4.6: - resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - - get-tsconfig@4.10.1: - resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} - - giget@2.0.0: - resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} - hasBin: true - - graphql@16.11.0: - resolution: {integrity: sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==} - engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} - - happy-dom@17.6.3: - resolution: {integrity: sha512-UVIHeVhxmxedbWPCfgS55Jg2rDfwf2BCKeylcPSqazLz5w3Kri7Q4xdBJubsr/+VUzFLh0VjIvh13RaDA2/Xug==} - engines: {node: '>=20.0.0'} - - headers-polyfill@4.0.3: - resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} - - hookable@5.5.3: - resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-node-process@1.2.0: - resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} - - jiti@2.4.2: - resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} - hasBin: true - - js-tokens@9.0.1: - resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - - jsonc-parser@3.3.1: - resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} - - loupe@3.1.3: - resolution: {integrity: sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==} - - magic-string@0.30.17: - resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - msw@2.10.2: - resolution: {integrity: sha512-RCKM6IZseZQCWcSWlutdf590M8nVfRHG1ImwzOtwz8IYxgT4zhUO0rfTcTvDGiaFE0Rhcc+h43lcF3Jc9gFtwQ==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - typescript: '>= 4.8.x' - peerDependenciesMeta: - typescript: - optional: true - - mute-stream@2.0.0: - resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} - engines: {node: ^18.17.0 || >=20.5.0} - - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - node-fetch-native@1.6.6: - resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==} - - nypm@0.6.0: - resolution: {integrity: sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg==} - engines: {node: ^14.16.0 || >=16.10.0} - hasBin: true - - ohash@2.0.11: - resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} - - on-exit-leak-free@2.1.2: - resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} - engines: {node: '>=14.0.0'} - - outvariant@1.4.3: - resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} - - package-manager-detector@1.3.0: - resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} - - path-to-regexp@6.3.0: - resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} - - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - - pathval@2.0.0: - resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} - engines: {node: '>= 14.16'} - - perfect-debounce@1.0.0: - resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@4.0.2: - resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} - engines: {node: '>=12'} - - pino-abstract-transport@2.0.0: - resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} - - pino-std-serializers@7.0.0: - resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} - - pino@9.7.0: - resolution: {integrity: sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==} - hasBin: true - - pkg-types@2.1.0: - resolution: {integrity: sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==} - - postcss@8.5.5: - resolution: {integrity: sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==} - engines: {node: ^10 || ^12 || >=14} - - process-warning@5.0.0: - resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} - - psl@1.15.0: - resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} - - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - quansync@0.2.10: - resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} - - querystringify@2.2.0: - resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - - quick-format-unescaped@4.0.4: - resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} - - rc9@2.1.2: - resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} - - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} - engines: {node: '>= 14.18.0'} - - real-require@0.2.0: - resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} - engines: {node: '>= 12.13.0'} - - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - - requires-port@1.0.0: - resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - - rolldown-plugin-dts@0.13.11: - resolution: {integrity: sha512-1TScN31JImk8xcq9kdm52z2W8/QX3zeDpEjFkyZmK+GcD0u8QqSWWARBsCEdfS99NyI6D9NIbUpsABXlcpZhig==} - engines: {node: '>=20.18.0'} - peerDependencies: - '@typescript/native-preview': '>=7.0.0-dev.20250601.1' - rolldown: ^1.0.0-beta.9 - typescript: ^5.0.0 - vue-tsc: ~2.2.0 - peerDependenciesMeta: - '@typescript/native-preview': - optional: true - typescript: - optional: true - vue-tsc: - optional: true - - rolldown@1.0.0-beta.9: - resolution: {integrity: sha512-ZgZky52n6iF0UainGKjptKGrOG4Con2S5sdc4C4y2Oj25D5PHAY8Y8E5f3M2TSd/zlhQs574JlMeTe3vREczSg==} - hasBin: true - peerDependencies: - '@oxc-project/runtime': 0.70.0 - peerDependenciesMeta: - '@oxc-project/runtime': - optional: true - - rollup@4.43.0: - resolution: {integrity: sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - safe-stable-stringify@2.5.0: - resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} - engines: {node: '>=10'} - - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} - engines: {node: '>=10'} - hasBin: true - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - sonic-boom@4.2.0: - resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - split2@4.2.0: - resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} - engines: {node: '>= 10.x'} - - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - - statuses@2.0.2: - resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} - engines: {node: '>= 0.8'} - - std-env@3.9.0: - resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} - - strict-event-emitter@0.5.1: - resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-literal@3.0.0: - resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} - - thread-stream@3.1.0: - resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} - - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - - tinyexec@1.0.1: - resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} - - tinyglobby@0.2.14: - resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} - engines: {node: '>=12.0.0'} - - tinypool@1.1.0: - resolution: {integrity: sha512-7CotroY9a8DKsKprEy/a14aCCm8jYVmR7aFy4fpkZM8sdpNJbKkixuNjgM50yCmip2ezc8z4N7k3oe2+rfRJCQ==} - engines: {node: ^18.0.0 || >=20.0.0} - - tinyrainbow@2.0.0: - resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} - engines: {node: '>=14.0.0'} - - tinyspy@4.0.3: - resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} - engines: {node: '>=14.0.0'} - - tough-cookie@4.1.4: - resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} - engines: {node: '>=6'} - - tsdown@0.11.13: - resolution: {integrity: sha512-VSfoNm8MJXFdg7PJ4p2javgjMRiQQHpkP9N3iBBTrmCixcT6YZ9ZtqYMW3NDHczqR0C0Qnur1HMQr1ZfZcmrng==} - engines: {node: '>=18.0.0'} - hasBin: true - peerDependencies: - publint: ^0.3.0 - typescript: ^5.0.0 - unplugin-lightningcss: ^0.4.0 - unplugin-unused: ^0.5.0 - peerDependenciesMeta: - publint: - optional: true - typescript: - optional: true - unplugin-lightningcss: - optional: true - unplugin-unused: - optional: true - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tsx@4.20.2: - resolution: {integrity: sha512-He0ZWr41gLa4vD30Au3yuwpe0HXaCZbclvl8RBieUiJ9aFnPMWUPIyvw3RU8+1Crjfcrauvitae2a4tUzRAGsw==} - engines: {node: '>=18.0.0'} - hasBin: true - - type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} - - type-fest@4.41.0: - resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} - engines: {node: '>=16'} - - typescript@5.8.3: - resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} - engines: {node: '>=14.17'} - hasBin: true - - unconfig@7.3.2: - resolution: {integrity: sha512-nqG5NNL2wFVGZ0NA/aCFw0oJ2pxSf1lwg4Z5ill8wd7K4KX/rQbHlwbh+bjctXL5Ly1xtzHenHGOK0b+lG6JVg==} - - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - - universalify@0.2.0: - resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} - engines: {node: '>= 4.0.0'} - - url-parse@1.5.10: - resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - - vite-node@3.2.3: - resolution: {integrity: sha512-gc8aAifGuDIpZHrPjuHyP4dpQmYXqWw7D1GmDnWeNWP654UEXzVfQ5IHPSK5HaHkwB/+p1atpYpSdw/2kOv8iQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - - vite@6.3.5: - resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - jiti: '>=1.21.0' - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - vitest@3.2.3: - resolution: {integrity: sha512-E6U2ZFXe3N/t4f5BwUaVCKRLHqUpk1CBWeMh78UT4VaTPH/2dyvH6ALl29JTovEPu9dVKr/K/J4PkXgrMbw4Ww==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.2.3 - '@vitest/ui': 3.2.3 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@types/debug': - optional: true - '@types/node': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - - webidl-conversions@7.0.0: - resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} - engines: {node: '>=12'} - - whatwg-mimetype@3.0.0: - resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} - engines: {node: '>=12'} - - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - - wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - ws@8.18.2: - resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - - yaml@2.8.0: - resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==} - engines: {node: '>= 14.6'} - hasBin: true - - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} - - yoctocolors-cjs@2.1.2: - resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} - engines: {node: '>=18'} - -snapshots: - - '@babel/generator@7.27.5': - dependencies: - '@babel/parser': 7.27.5 - '@babel/types': 7.27.6 - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 3.1.0 - - '@babel/helper-string-parser@7.27.1': {} - - '@babel/helper-validator-identifier@7.27.1': {} - - '@babel/parser@7.27.5': - dependencies: - '@babel/types': 7.27.6 - - '@babel/types@7.27.6': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - - '@biomejs/biome@1.9.4': - optionalDependencies: - '@biomejs/cli-darwin-arm64': 1.9.4 - '@biomejs/cli-darwin-x64': 1.9.4 - '@biomejs/cli-linux-arm64': 1.9.4 - '@biomejs/cli-linux-arm64-musl': 1.9.4 - '@biomejs/cli-linux-x64': 1.9.4 - '@biomejs/cli-linux-x64-musl': 1.9.4 - '@biomejs/cli-win32-arm64': 1.9.4 - '@biomejs/cli-win32-x64': 1.9.4 - - '@biomejs/cli-darwin-arm64@1.9.4': - optional: true - - '@biomejs/cli-darwin-x64@1.9.4': - optional: true - - '@biomejs/cli-linux-arm64-musl@1.9.4': - optional: true - - '@biomejs/cli-linux-arm64@1.9.4': - optional: true - - '@biomejs/cli-linux-x64-musl@1.9.4': - optional: true - - '@biomejs/cli-linux-x64@1.9.4': - optional: true - - '@biomejs/cli-win32-arm64@1.9.4': - optional: true - - '@biomejs/cli-win32-x64@1.9.4': - optional: true - - '@bundled-es-modules/cookie@2.0.1': - dependencies: - cookie: 0.7.2 - optional: true - - '@bundled-es-modules/statuses@1.0.1': - dependencies: - statuses: 2.0.2 - optional: true - - '@bundled-es-modules/tough-cookie@0.1.6': - dependencies: - '@types/tough-cookie': 4.0.5 - tough-cookie: 4.1.4 - optional: true - - '@emnapi/core@1.4.3': - dependencies: - '@emnapi/wasi-threads': 1.0.2 - tslib: 2.8.1 - optional: true - - '@emnapi/runtime@1.4.3': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@1.0.2': - dependencies: - tslib: 2.8.1 - optional: true - - '@esbuild/aix-ppc64@0.25.5': - optional: true - - '@esbuild/android-arm64@0.25.5': - optional: true - - '@esbuild/android-arm@0.25.5': - optional: true - - '@esbuild/android-x64@0.25.5': - optional: true - - '@esbuild/darwin-arm64@0.25.5': - optional: true - - '@esbuild/darwin-x64@0.25.5': - optional: true - - '@esbuild/freebsd-arm64@0.25.5': - optional: true - - '@esbuild/freebsd-x64@0.25.5': - optional: true - - '@esbuild/linux-arm64@0.25.5': - optional: true - - '@esbuild/linux-arm@0.25.5': - optional: true - - '@esbuild/linux-ia32@0.25.5': - optional: true - - '@esbuild/linux-loong64@0.25.5': - optional: true - - '@esbuild/linux-mips64el@0.25.5': - optional: true - - '@esbuild/linux-ppc64@0.25.5': - optional: true - - '@esbuild/linux-riscv64@0.25.5': - optional: true - - '@esbuild/linux-s390x@0.25.5': - optional: true - - '@esbuild/linux-x64@0.25.5': - optional: true - - '@esbuild/netbsd-arm64@0.25.5': - optional: true - - '@esbuild/netbsd-x64@0.25.5': - optional: true - - '@esbuild/openbsd-arm64@0.25.5': - optional: true - - '@esbuild/openbsd-x64@0.25.5': - optional: true - - '@esbuild/sunos-x64@0.25.5': - optional: true - - '@esbuild/win32-arm64@0.25.5': - optional: true - - '@esbuild/win32-ia32@0.25.5': - optional: true - - '@esbuild/win32-x64@0.25.5': - optional: true - - '@inquirer/confirm@5.1.12(@types/node@22.15.31)': - dependencies: - '@inquirer/core': 10.1.13(@types/node@22.15.31) - '@inquirer/type': 3.0.7(@types/node@22.15.31) - optionalDependencies: - '@types/node': 22.15.31 - optional: true - - '@inquirer/core@10.1.13(@types/node@22.15.31)': - dependencies: - '@inquirer/figures': 1.0.12 - '@inquirer/type': 3.0.7(@types/node@22.15.31) - ansi-escapes: 4.3.2 - cli-width: 4.1.0 - mute-stream: 2.0.0 - signal-exit: 4.1.0 - wrap-ansi: 6.2.0 - yoctocolors-cjs: 2.1.2 - optionalDependencies: - '@types/node': 22.15.31 - optional: true - - '@inquirer/figures@1.0.12': - optional: true - - '@inquirer/type@3.0.7(@types/node@22.15.31)': - optionalDependencies: - '@types/node': 22.15.31 - optional: true - - '@jridgewell/gen-mapping@0.3.8': - dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/set-array@1.2.1': {} - - '@jridgewell/sourcemap-codec@1.5.0': {} - - '@jridgewell/trace-mapping@0.3.25': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 - - '@mswjs/interceptors@0.39.2': - dependencies: - '@open-draft/deferred-promise': 2.2.0 - '@open-draft/logger': 0.3.0 - '@open-draft/until': 2.1.0 - is-node-process: 1.2.0 - outvariant: 1.4.3 - strict-event-emitter: 0.5.1 - optional: true - - '@napi-rs/wasm-runtime@0.2.11': - dependencies: - '@emnapi/core': 1.4.3 - '@emnapi/runtime': 1.4.3 - '@tybys/wasm-util': 0.9.0 - optional: true - - '@open-draft/deferred-promise@2.2.0': - optional: true - - '@open-draft/logger@0.3.0': - dependencies: - is-node-process: 1.2.0 - outvariant: 1.4.3 - optional: true - - '@open-draft/until@2.1.0': - optional: true - - '@oxc-project/types@0.70.0': {} - - '@quansync/fs@0.1.3': - dependencies: - quansync: 0.2.10 - - '@rolldown/binding-darwin-arm64@1.0.0-beta.9': - optional: true - - '@rolldown/binding-darwin-x64@1.0.0-beta.9': - optional: true - - '@rolldown/binding-freebsd-x64@1.0.0-beta.9': - optional: true - - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9': - optional: true - - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9': - optional: true - - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.9': - optional: true - - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.9': - optional: true - - '@rolldown/binding-linux-x64-musl@1.0.0-beta.9': - optional: true - - '@rolldown/binding-wasm32-wasi@1.0.0-beta.9': - dependencies: - '@napi-rs/wasm-runtime': 0.2.11 - optional: true - - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9': - optional: true - - '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9': - optional: true - - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.9': - optional: true - - '@rolldown/pluginutils@1.0.0-beta.9': {} - - '@rollup/rollup-android-arm-eabi@4.43.0': - optional: true - - '@rollup/rollup-android-arm64@4.43.0': - optional: true - - '@rollup/rollup-darwin-arm64@4.43.0': - optional: true - - '@rollup/rollup-darwin-x64@4.43.0': - optional: true - - '@rollup/rollup-freebsd-arm64@4.43.0': - optional: true - - '@rollup/rollup-freebsd-x64@4.43.0': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.43.0': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.43.0': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.43.0': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.43.0': - optional: true - - '@rollup/rollup-linux-loongarch64-gnu@4.43.0': - optional: true - - '@rollup/rollup-linux-powerpc64le-gnu@4.43.0': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.43.0': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.43.0': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.43.0': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.43.0': - optional: true - - '@rollup/rollup-linux-x64-musl@4.43.0': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.43.0': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.43.0': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.43.0': - optional: true - - '@tybys/wasm-util@0.9.0': - dependencies: - tslib: 2.8.1 - optional: true - - '@types/chai@5.2.2': - dependencies: - '@types/deep-eql': 4.0.2 - - '@types/cookie@0.6.0': - optional: true - - '@types/debug@4.1.12': - dependencies: - '@types/ms': 2.1.0 - optional: true - - '@types/deep-eql@4.0.2': {} - - '@types/estree@1.0.7': {} - - '@types/estree@1.0.8': {} - - '@types/ms@2.1.0': - optional: true - - '@types/node@22.15.31': - dependencies: - undici-types: 6.21.0 - - '@types/statuses@2.0.6': - optional: true - - '@types/tough-cookie@4.0.5': - optional: true - - '@types/ws@8.18.1': - dependencies: - '@types/node': 22.15.31 - - '@vitest/expect@3.2.3': - dependencies: - '@types/chai': 5.2.2 - '@vitest/spy': 3.2.3 - '@vitest/utils': 3.2.3 - chai: 5.2.0 - tinyrainbow: 2.0.0 - - '@vitest/mocker@3.2.3(msw@2.10.2(@types/node@22.15.31)(typescript@5.8.3))(vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0))': - dependencies: - '@vitest/spy': 3.2.3 - estree-walker: 3.0.3 - magic-string: 0.30.17 - optionalDependencies: - msw: 2.10.2(@types/node@22.15.31)(typescript@5.8.3) - vite: 6.3.5(@types/node@22.15.31)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0) - - '@vitest/pretty-format@3.2.3': - dependencies: - tinyrainbow: 2.0.0 - - '@vitest/runner@3.2.3': - dependencies: - '@vitest/utils': 3.2.3 - pathe: 2.0.3 - strip-literal: 3.0.0 - - '@vitest/snapshot@3.2.3': - dependencies: - '@vitest/pretty-format': 3.2.3 - magic-string: 0.30.17 - pathe: 2.0.3 - - '@vitest/spy@3.2.3': - dependencies: - tinyspy: 4.0.3 - - '@vitest/utils@3.2.3': - dependencies: - '@vitest/pretty-format': 3.2.3 - loupe: 3.1.3 - tinyrainbow: 2.0.0 - - ansi-escapes@4.3.2: - dependencies: - type-fest: 0.21.3 - optional: true - - ansi-regex@5.0.1: - optional: true - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - optional: true - - ansis@4.1.0: {} - - args-tokenizer@0.3.0: {} - - assertion-error@2.0.1: {} - - ast-kit@2.1.0: - dependencies: - '@babel/parser': 7.27.5 - pathe: 2.0.3 - - atomic-sleep@1.0.0: {} - - birpc@2.3.0: {} - - bumpp@10.1.1: - dependencies: - ansis: 4.1.0 - args-tokenizer: 0.3.0 - c12: 3.0.4 - cac: 6.7.14 - escalade: 3.2.0 - jsonc-parser: 3.3.1 - package-manager-detector: 1.3.0 - semver: 7.7.2 - tinyexec: 1.0.1 - tinyglobby: 0.2.14 - yaml: 2.8.0 - transitivePeerDependencies: - - magicast - - c12@3.0.4: - dependencies: - chokidar: 4.0.3 - confbox: 0.2.2 - defu: 6.1.4 - dotenv: 16.5.0 - exsolve: 1.0.5 - giget: 2.0.0 - jiti: 2.4.2 - ohash: 2.0.11 - pathe: 2.0.3 - perfect-debounce: 1.0.0 - pkg-types: 2.1.0 - rc9: 2.1.2 - - cac@6.7.14: {} - - chai@5.2.0: - dependencies: - assertion-error: 2.0.1 - check-error: 2.1.1 - deep-eql: 5.0.2 - loupe: 3.1.3 - pathval: 2.0.0 - - check-error@2.1.1: {} - - chokidar@4.0.3: - dependencies: - readdirp: 4.1.2 - - citty@0.1.6: - dependencies: - consola: 3.4.2 - - cli-width@4.1.0: - optional: true - - cliui@8.0.1: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - optional: true - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - optional: true - - color-name@1.1.4: - optional: true - - confbox@0.2.2: {} - - consola@3.4.2: {} - - cookie@0.7.2: - optional: true - - debug@4.4.1: - dependencies: - ms: 2.1.3 - - deep-eql@5.0.2: {} - - defu@6.1.4: {} - - destr@2.0.5: {} - - diff@8.0.2: {} - - dotenv@16.5.0: {} - - dts-resolver@2.1.1: {} - - emoji-regex@8.0.0: - optional: true - - empathic@1.1.0: {} - - es-module-lexer@1.7.0: {} - - esbuild@0.25.5: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.5 - '@esbuild/android-arm': 0.25.5 - '@esbuild/android-arm64': 0.25.5 - '@esbuild/android-x64': 0.25.5 - '@esbuild/darwin-arm64': 0.25.5 - '@esbuild/darwin-x64': 0.25.5 - '@esbuild/freebsd-arm64': 0.25.5 - '@esbuild/freebsd-x64': 0.25.5 - '@esbuild/linux-arm': 0.25.5 - '@esbuild/linux-arm64': 0.25.5 - '@esbuild/linux-ia32': 0.25.5 - '@esbuild/linux-loong64': 0.25.5 - '@esbuild/linux-mips64el': 0.25.5 - '@esbuild/linux-ppc64': 0.25.5 - '@esbuild/linux-riscv64': 0.25.5 - '@esbuild/linux-s390x': 0.25.5 - '@esbuild/linux-x64': 0.25.5 - '@esbuild/netbsd-arm64': 0.25.5 - '@esbuild/netbsd-x64': 0.25.5 - '@esbuild/openbsd-arm64': 0.25.5 - '@esbuild/openbsd-x64': 0.25.5 - '@esbuild/sunos-x64': 0.25.5 - '@esbuild/win32-arm64': 0.25.5 - '@esbuild/win32-ia32': 0.25.5 - '@esbuild/win32-x64': 0.25.5 - - escalade@3.2.0: {} - - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.8 - - expect-type@1.2.1: {} - - exsolve@1.0.5: {} - - fast-redact@3.5.0: {} - - fdir@6.4.6(picomatch@4.0.2): - optionalDependencies: - picomatch: 4.0.2 - - fsevents@2.3.3: - optional: true - - get-caller-file@2.0.5: - optional: true - - get-tsconfig@4.10.1: - dependencies: - resolve-pkg-maps: 1.0.0 - - giget@2.0.0: - dependencies: - citty: 0.1.6 - consola: 3.4.2 - defu: 6.1.4 - node-fetch-native: 1.6.6 - nypm: 0.6.0 - pathe: 2.0.3 - - graphql@16.11.0: - optional: true - - happy-dom@17.6.3: - dependencies: - webidl-conversions: 7.0.0 - whatwg-mimetype: 3.0.0 - - headers-polyfill@4.0.3: - optional: true - - hookable@5.5.3: {} - - is-fullwidth-code-point@3.0.0: - optional: true - - is-node-process@1.2.0: - optional: true - - jiti@2.4.2: {} - - js-tokens@9.0.1: {} - - jsesc@3.1.0: {} - - jsonc-parser@3.3.1: {} - - loupe@3.1.3: {} - - magic-string@0.30.17: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 - - ms@2.1.3: {} - - msw@2.10.2(@types/node@22.15.31)(typescript@5.8.3): - dependencies: - '@bundled-es-modules/cookie': 2.0.1 - '@bundled-es-modules/statuses': 1.0.1 - '@bundled-es-modules/tough-cookie': 0.1.6 - '@inquirer/confirm': 5.1.12(@types/node@22.15.31) - '@mswjs/interceptors': 0.39.2 - '@open-draft/deferred-promise': 2.2.0 - '@open-draft/until': 2.1.0 - '@types/cookie': 0.6.0 - '@types/statuses': 2.0.6 - graphql: 16.11.0 - headers-polyfill: 4.0.3 - is-node-process: 1.2.0 - outvariant: 1.4.3 - path-to-regexp: 6.3.0 - picocolors: 1.1.1 - strict-event-emitter: 0.5.1 - type-fest: 4.41.0 - yargs: 17.7.2 - optionalDependencies: - typescript: 5.8.3 - transitivePeerDependencies: - - '@types/node' - optional: true - - mute-stream@2.0.0: - optional: true - - nanoid@3.3.11: {} - - node-fetch-native@1.6.6: {} - - nypm@0.6.0: - dependencies: - citty: 0.1.6 - consola: 3.4.2 - pathe: 2.0.3 - pkg-types: 2.1.0 - tinyexec: 0.3.2 - - ohash@2.0.11: {} - - on-exit-leak-free@2.1.2: {} - - outvariant@1.4.3: - optional: true - - package-manager-detector@1.3.0: {} - - path-to-regexp@6.3.0: - optional: true - - pathe@2.0.3: {} - - pathval@2.0.0: {} - - perfect-debounce@1.0.0: {} - - picocolors@1.1.1: {} - - picomatch@4.0.2: {} - - pino-abstract-transport@2.0.0: - dependencies: - split2: 4.2.0 - - pino-std-serializers@7.0.0: {} - - pino@9.7.0: - dependencies: - atomic-sleep: 1.0.0 - fast-redact: 3.5.0 - on-exit-leak-free: 2.1.2 - pino-abstract-transport: 2.0.0 - pino-std-serializers: 7.0.0 - process-warning: 5.0.0 - quick-format-unescaped: 4.0.4 - real-require: 0.2.0 - safe-stable-stringify: 2.5.0 - sonic-boom: 4.2.0 - thread-stream: 3.1.0 - - pkg-types@2.1.0: - dependencies: - confbox: 0.2.2 - exsolve: 1.0.5 - pathe: 2.0.3 - - postcss@8.5.5: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - process-warning@5.0.0: {} - - psl@1.15.0: - dependencies: - punycode: 2.3.1 - optional: true - - punycode@2.3.1: - optional: true - - quansync@0.2.10: {} - - querystringify@2.2.0: - optional: true - - quick-format-unescaped@4.0.4: {} - - rc9@2.1.2: - dependencies: - defu: 6.1.4 - destr: 2.0.5 - - readdirp@4.1.2: {} - - real-require@0.2.0: {} - - require-directory@2.1.1: - optional: true - - requires-port@1.0.0: - optional: true - - resolve-pkg-maps@1.0.0: {} - - rolldown-plugin-dts@0.13.11(rolldown@1.0.0-beta.9)(typescript@5.8.3): - dependencies: - '@babel/generator': 7.27.5 - '@babel/parser': 7.27.5 - '@babel/types': 7.27.6 - ast-kit: 2.1.0 - birpc: 2.3.0 - debug: 4.4.1 - dts-resolver: 2.1.1 - get-tsconfig: 4.10.1 - rolldown: 1.0.0-beta.9 - optionalDependencies: - typescript: 5.8.3 - transitivePeerDependencies: - - oxc-resolver - - supports-color - - rolldown@1.0.0-beta.9: - dependencies: - '@oxc-project/types': 0.70.0 - '@rolldown/pluginutils': 1.0.0-beta.9 - ansis: 4.1.0 - optionalDependencies: - '@rolldown/binding-darwin-arm64': 1.0.0-beta.9 - '@rolldown/binding-darwin-x64': 1.0.0-beta.9 - '@rolldown/binding-freebsd-x64': 1.0.0-beta.9 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.9 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.9 - '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.9 - '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.9 - '@rolldown/binding-linux-x64-musl': 1.0.0-beta.9 - '@rolldown/binding-wasm32-wasi': 1.0.0-beta.9 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.9 - '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.9 - '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.9 - - rollup@4.43.0: - dependencies: - '@types/estree': 1.0.7 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.43.0 - '@rollup/rollup-android-arm64': 4.43.0 - '@rollup/rollup-darwin-arm64': 4.43.0 - '@rollup/rollup-darwin-x64': 4.43.0 - '@rollup/rollup-freebsd-arm64': 4.43.0 - '@rollup/rollup-freebsd-x64': 4.43.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.43.0 - '@rollup/rollup-linux-arm-musleabihf': 4.43.0 - '@rollup/rollup-linux-arm64-gnu': 4.43.0 - '@rollup/rollup-linux-arm64-musl': 4.43.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.43.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.43.0 - '@rollup/rollup-linux-riscv64-gnu': 4.43.0 - '@rollup/rollup-linux-riscv64-musl': 4.43.0 - '@rollup/rollup-linux-s390x-gnu': 4.43.0 - '@rollup/rollup-linux-x64-gnu': 4.43.0 - '@rollup/rollup-linux-x64-musl': 4.43.0 - '@rollup/rollup-win32-arm64-msvc': 4.43.0 - '@rollup/rollup-win32-ia32-msvc': 4.43.0 - '@rollup/rollup-win32-x64-msvc': 4.43.0 - fsevents: 2.3.3 - - safe-stable-stringify@2.5.0: {} - - semver@7.7.2: {} - - siginfo@2.0.0: {} - - signal-exit@4.1.0: - optional: true - - sonic-boom@4.2.0: - dependencies: - atomic-sleep: 1.0.0 - - source-map-js@1.2.1: {} - - split2@4.2.0: {} - - stackback@0.0.2: {} - - statuses@2.0.2: - optional: true - - std-env@3.9.0: {} - - strict-event-emitter@0.5.1: - optional: true - - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - optional: true - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - optional: true - - strip-literal@3.0.0: - dependencies: - js-tokens: 9.0.1 - - thread-stream@3.1.0: - dependencies: - real-require: 0.2.0 - - tinybench@2.9.0: {} - - tinyexec@0.3.2: {} - - tinyexec@1.0.1: {} - - tinyglobby@0.2.14: - dependencies: - fdir: 6.4.6(picomatch@4.0.2) - picomatch: 4.0.2 - - tinypool@1.1.0: {} - - tinyrainbow@2.0.0: {} - - tinyspy@4.0.3: {} - - tough-cookie@4.1.4: - dependencies: - psl: 1.15.0 - punycode: 2.3.1 - universalify: 0.2.0 - url-parse: 1.5.10 - optional: true - - tsdown@0.11.13(typescript@5.8.3): - dependencies: - ansis: 4.1.0 - cac: 6.7.14 - chokidar: 4.0.3 - debug: 4.4.1 - diff: 8.0.2 - empathic: 1.1.0 - hookable: 5.5.3 - rolldown: 1.0.0-beta.9 - rolldown-plugin-dts: 0.13.11(rolldown@1.0.0-beta.9)(typescript@5.8.3) - semver: 7.7.2 - tinyexec: 1.0.1 - tinyglobby: 0.2.14 - unconfig: 7.3.2 - optionalDependencies: - typescript: 5.8.3 - transitivePeerDependencies: - - '@oxc-project/runtime' - - '@typescript/native-preview' - - oxc-resolver - - supports-color - - vue-tsc - - tslib@2.8.1: - optional: true - - tsx@4.20.2: - dependencies: - esbuild: 0.25.5 - get-tsconfig: 4.10.1 - optionalDependencies: - fsevents: 2.3.3 - - type-fest@0.21.3: - optional: true - - type-fest@4.41.0: - optional: true - - typescript@5.8.3: {} - - unconfig@7.3.2: - dependencies: - '@quansync/fs': 0.1.3 - defu: 6.1.4 - jiti: 2.4.2 - quansync: 0.2.10 - - undici-types@6.21.0: {} - - universalify@0.2.0: - optional: true - - url-parse@1.5.10: - dependencies: - querystringify: 2.2.0 - requires-port: 1.0.0 - optional: true - - vite-node@3.2.3(@types/node@22.15.31)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0): - dependencies: - cac: 6.7.14 - debug: 4.4.1 - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 6.3.5(@types/node@22.15.31)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0): - dependencies: - esbuild: 0.25.5 - fdir: 6.4.6(picomatch@4.0.2) - picomatch: 4.0.2 - postcss: 8.5.5 - rollup: 4.43.0 - tinyglobby: 0.2.14 - optionalDependencies: - '@types/node': 22.15.31 - fsevents: 2.3.3 - jiti: 2.4.2 - tsx: 4.20.2 - yaml: 2.8.0 - - vitest@3.2.3(@types/debug@4.1.12)(@types/node@22.15.31)(happy-dom@17.6.3)(jiti@2.4.2)(msw@2.10.2(@types/node@22.15.31)(typescript@5.8.3))(tsx@4.20.2)(yaml@2.8.0): - dependencies: - '@types/chai': 5.2.2 - '@vitest/expect': 3.2.3 - '@vitest/mocker': 3.2.3(msw@2.10.2(@types/node@22.15.31)(typescript@5.8.3))(vite@6.3.5(@types/node@22.15.31)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0)) - '@vitest/pretty-format': 3.2.3 - '@vitest/runner': 3.2.3 - '@vitest/snapshot': 3.2.3 - '@vitest/spy': 3.2.3 - '@vitest/utils': 3.2.3 - chai: 5.2.0 - debug: 4.4.1 - expect-type: 1.2.1 - magic-string: 0.30.17 - pathe: 2.0.3 - picomatch: 4.0.2 - std-env: 3.9.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.14 - tinypool: 1.1.0 - tinyrainbow: 2.0.0 - vite: 6.3.5(@types/node@22.15.31)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0) - vite-node: 3.2.3(@types/node@22.15.31)(jiti@2.4.2)(tsx@4.20.2)(yaml@2.8.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/debug': 4.1.12 - '@types/node': 22.15.31 - happy-dom: 17.6.3 - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - webidl-conversions@7.0.0: {} - - whatwg-mimetype@3.0.0: {} - - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - - wrap-ansi@6.2.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - optional: true - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - optional: true - - ws@8.18.2: {} - - y18n@5.0.8: - optional: true - - yaml@2.8.0: {} - - yargs-parser@21.1.1: - optional: true - - yargs@17.7.2: - dependencies: - cliui: 8.0.1 - escalade: 3.2.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 21.1.1 - optional: true - - yoctocolors-cjs@2.1.2: - optional: true diff --git a/libs/typescript/core/pnpm-lock.yaml b/libs/typescript/core/pnpm-lock.yaml deleted file mode 100644 index 3f72d007..00000000 --- a/libs/typescript/core/pnpm-lock.yaml +++ /dev/null @@ -1,1863 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@types/uuid': - specifier: ^10.0.0 - version: 10.0.0 - pino: - specifier: ^9.7.0 - version: 9.7.0 - posthog-node: - specifier: ^5.1.1 - version: 5.1.1 - uuid: - specifier: ^11.1.0 - version: 11.1.0 - devDependencies: - '@biomejs/biome': - specifier: ^1.9.4 - version: 1.9.4 - '@types/node': - specifier: ^22.15.17 - version: 22.15.32 - '@types/ws': - specifier: ^8.18.1 - version: 8.18.1 - bumpp: - specifier: ^10.1.0 - version: 10.2.0 - happy-dom: - specifier: ^17.4.7 - version: 17.6.3 - tsdown: - specifier: ^0.11.9 - version: 0.11.13(typescript@5.8.3) - tsx: - specifier: ^4.19.4 - version: 4.20.3 - typescript: - specifier: ^5.8.3 - version: 5.8.3 - vitest: - specifier: ^3.1.3 - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.32)(happy-dom@17.6.3)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) - -packages: - - '@babel/generator@7.27.5': - resolution: {integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.27.1': - resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.27.5': - resolution: {integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/types@7.27.6': - resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==} - engines: {node: '>=6.9.0'} - - '@biomejs/biome@1.9.4': - resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} - engines: {node: '>=14.21.3'} - hasBin: true - - '@biomejs/cli-darwin-arm64@1.9.4': - resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} - engines: {node: '>=14.21.3'} - cpu: [arm64] - os: [darwin] - - '@biomejs/cli-darwin-x64@1.9.4': - resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==} - engines: {node: '>=14.21.3'} - cpu: [x64] - os: [darwin] - - '@biomejs/cli-linux-arm64-musl@1.9.4': - resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==} - engines: {node: '>=14.21.3'} - cpu: [arm64] - os: [linux] - - '@biomejs/cli-linux-arm64@1.9.4': - resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==} - engines: {node: '>=14.21.3'} - cpu: [arm64] - os: [linux] - - '@biomejs/cli-linux-x64-musl@1.9.4': - resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==} - engines: {node: '>=14.21.3'} - cpu: [x64] - os: [linux] - - '@biomejs/cli-linux-x64@1.9.4': - resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==} - engines: {node: '>=14.21.3'} - cpu: [x64] - os: [linux] - - '@biomejs/cli-win32-arm64@1.9.4': - resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==} - engines: {node: '>=14.21.3'} - cpu: [arm64] - os: [win32] - - '@biomejs/cli-win32-x64@1.9.4': - resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==} - engines: {node: '>=14.21.3'} - cpu: [x64] - os: [win32] - - '@emnapi/core@1.4.3': - resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} - - '@emnapi/runtime@1.4.3': - resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} - - '@emnapi/wasi-threads@1.0.2': - resolution: {integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==} - - '@esbuild/aix-ppc64@0.25.5': - resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.25.5': - resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.25.5': - resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.25.5': - resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.25.5': - resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.25.5': - resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.25.5': - resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.25.5': - resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.25.5': - resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.25.5': - resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.25.5': - resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.25.5': - resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.25.5': - resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.25.5': - resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.25.5': - resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.25.5': - resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.25.5': - resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.25.5': - resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.25.5': - resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.25.5': - resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.25.5': - resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/sunos-x64@0.25.5': - resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.25.5': - resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.25.5': - resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.25.5': - resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@jridgewell/gen-mapping@0.3.8': - resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} - engines: {node: '>=6.0.0'} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - - '@napi-rs/wasm-runtime@0.2.11': - resolution: {integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==} - - '@oxc-project/types@0.70.0': - resolution: {integrity: sha512-ngyLUpUjO3dpqygSRQDx7nMx8+BmXbWOU4oIwTJFV2MVIDG7knIZwgdwXlQWLg3C3oxg1lS7ppMtPKqKFb7wzw==} - - '@quansync/fs@0.1.3': - resolution: {integrity: sha512-G0OnZbMWEs5LhDyqy2UL17vGhSVHkQIfVojMtEWVenvj0V5S84VBgy86kJIuNsGDp2p7sTKlpSIpBUWdC35OKg==} - engines: {node: '>=20.0.0'} - - '@rolldown/binding-darwin-arm64@1.0.0-beta.9': - resolution: {integrity: sha512-geUG/FUpm+membLC0NQBb39vVyOfguYZ2oyXc7emr6UjH6TeEECT4b0CPZXKFnELareTiU/Jfl70/eEgNxyQeA==} - cpu: [arm64] - os: [darwin] - - '@rolldown/binding-darwin-x64@1.0.0-beta.9': - resolution: {integrity: sha512-7wPXDwcOtv2I+pWTL2UNpNAxMAGukgBT90Jz4DCfwaYdGvQncF7J0S7IWrRVsRFhBavxM+65RcueE3VXw5UIbg==} - cpu: [x64] - os: [darwin] - - '@rolldown/binding-freebsd-x64@1.0.0-beta.9': - resolution: {integrity: sha512-agO5mONTNKVrcIt4SRxw5Ni0FOVV3gaH8dIiNp1A4JeU91b9kw7x+JRuNJAQuM2X3pYqVvA6qh13UTNOsaqM/Q==} - cpu: [x64] - os: [freebsd] - - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9': - resolution: {integrity: sha512-dDNDV9p/8WYDriS9HCcbH6y6+JP38o3enj/pMkdkmkxEnZ0ZoHIfQ9RGYWeRYU56NKBCrya4qZBJx49Jk9LRug==} - cpu: [arm] - os: [linux] - - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9': - resolution: {integrity: sha512-kZKegmHG1ZvfsFIwYU6DeFSxSIcIliXzeznsJHUo9D9/dlVSDi/PUvsRKcuJkQjZoejM6pk8MHN/UfgGdIhPHw==} - cpu: [arm64] - os: [linux] - - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.9': - resolution: {integrity: sha512-f+VL8mO31pyMJiJPr2aA1ryYONkP2UqgbwK7fKtKHZIeDd/AoUGn3+ujPqDhuy2NxgcJ5H8NaSvDpG1tJMHh+g==} - cpu: [arm64] - os: [linux] - - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.9': - resolution: {integrity: sha512-GiUEZ0WPjX5LouDoC3O8aJa4h6BLCpIvaAboNw5JoRour/3dC6rbtZZ/B5FC3/ySsN3/dFOhAH97ylQxoZJi7A==} - cpu: [x64] - os: [linux] - - '@rolldown/binding-linux-x64-musl@1.0.0-beta.9': - resolution: {integrity: sha512-AMb0dicw+QHh6RxvWo4BRcuTMgS0cwUejJRMpSyIcHYnKTbj6nUW4HbWNQuDfZiF27l6F5gEwBS+YLUdVzL9vg==} - cpu: [x64] - os: [linux] - - '@rolldown/binding-wasm32-wasi@1.0.0-beta.9': - resolution: {integrity: sha512-+pdaiTx7L8bWKvsAuCE0HAxP1ze1WOLoWGCawcrZbMSY10dMh2i82lJiH6tXGXbfYYwsNWhWE2NyG4peFZvRfQ==} - engines: {node: '>=14.21.3'} - cpu: [wasm32] - - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9': - resolution: {integrity: sha512-A7kN248viWvb8eZMzQu024TBKGoyoVYBsDG2DtoP8u2pzwoh5yDqUL291u01o4f8uzpUHq8mfwQJmcGChFu8KQ==} - cpu: [arm64] - os: [win32] - - '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9': - resolution: {integrity: sha512-DzKN7iEYjAP8AK8F2G2aCej3fk43Y/EQrVrR3gF0XREes56chjQ7bXIhw819jv74BbxGdnpPcslhet/cgt7WRA==} - cpu: [ia32] - os: [win32] - - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.9': - resolution: {integrity: sha512-GMWgTvvbZ8TfBsAiJpoz4SRq3IN3aUMn0rYm8q4I8dcEk4J1uISyfb6ZMzvqW+cvScTWVKWZNqnrmYOKLLUt4w==} - cpu: [x64] - os: [win32] - - '@rolldown/pluginutils@1.0.0-beta.9': - resolution: {integrity: sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==} - - '@rollup/rollup-android-arm-eabi@4.44.0': - resolution: {integrity: sha512-xEiEE5oDW6tK4jXCAyliuntGR+amEMO7HLtdSshVuhFnKTYoeYMyXQK7pLouAJJj5KHdwdn87bfHAR2nSdNAUA==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.44.0': - resolution: {integrity: sha512-uNSk/TgvMbskcHxXYHzqwiyBlJ/lGcv8DaUfcnNwict8ba9GTTNxfn3/FAoFZYgkaXXAdrAA+SLyKplyi349Jw==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.44.0': - resolution: {integrity: sha512-VGF3wy0Eq1gcEIkSCr8Ke03CWT+Pm2yveKLaDvq51pPpZza3JX/ClxXOCmTYYq3us5MvEuNRTaeyFThCKRQhOA==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.44.0': - resolution: {integrity: sha512-fBkyrDhwquRvrTxSGH/qqt3/T0w5Rg0L7ZIDypvBPc1/gzjJle6acCpZ36blwuwcKD/u6oCE/sRWlUAcxLWQbQ==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.44.0': - resolution: {integrity: sha512-u5AZzdQJYJXByB8giQ+r4VyfZP+walV+xHWdaFx/1VxsOn6eWJhK2Vl2eElvDJFKQBo/hcYIBg/jaKS8ZmKeNQ==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.44.0': - resolution: {integrity: sha512-qC0kS48c/s3EtdArkimctY7h3nHicQeEUdjJzYVJYR3ct3kWSafmn6jkNCA8InbUdge6PVx6keqjk5lVGJf99g==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.44.0': - resolution: {integrity: sha512-x+e/Z9H0RAWckn4V2OZZl6EmV0L2diuX3QB0uM1r6BvhUIv6xBPL5mrAX2E3e8N8rEHVPwFfz/ETUbV4oW9+lQ==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.44.0': - resolution: {integrity: sha512-1exwiBFf4PU/8HvI8s80icyCcnAIB86MCBdst51fwFmH5dyeoWVPVgmQPcKrMtBQ0W5pAs7jBCWuRXgEpRzSCg==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.44.0': - resolution: {integrity: sha512-ZTR2mxBHb4tK4wGf9b8SYg0Y6KQPjGpR4UWwTFdnmjB4qRtoATZ5dWn3KsDwGa5Z2ZBOE7K52L36J9LueKBdOQ==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-musl@4.44.0': - resolution: {integrity: sha512-GFWfAhVhWGd4r6UxmnKRTBwP1qmModHtd5gkraeW2G490BpFOZkFtem8yuX2NyafIP/mGpRJgTJ2PwohQkUY/Q==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-loongarch64-gnu@4.44.0': - resolution: {integrity: sha512-xw+FTGcov/ejdusVOqKgMGW3c4+AgqrfvzWEVXcNP6zq2ue+lsYUgJ+5Rtn/OTJf7e2CbgTFvzLW2j0YAtj0Gg==} - cpu: [loong64] - os: [linux] - - '@rollup/rollup-linux-powerpc64le-gnu@4.44.0': - resolution: {integrity: sha512-bKGibTr9IdF0zr21kMvkZT4K6NV+jjRnBoVMt2uNMG0BYWm3qOVmYnXKzx7UhwrviKnmK46IKMByMgvpdQlyJQ==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.44.0': - resolution: {integrity: sha512-vV3cL48U5kDaKZtXrti12YRa7TyxgKAIDoYdqSIOMOFBXqFj2XbChHAtXquEn2+n78ciFgr4KIqEbydEGPxXgA==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-riscv64-musl@4.44.0': - resolution: {integrity: sha512-TDKO8KlHJuvTEdfw5YYFBjhFts2TR0VpZsnLLSYmB7AaohJhM8ctDSdDnUGq77hUh4m/djRafw+9zQpkOanE2Q==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.44.0': - resolution: {integrity: sha512-8541GEyktXaw4lvnGp9m84KENcxInhAt6vPWJ9RodsB/iGjHoMB2Pp5MVBCiKIRxrxzJhGCxmNzdu+oDQ7kwRA==} - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.44.0': - resolution: {integrity: sha512-iUVJc3c0o8l9Sa/qlDL2Z9UP92UZZW1+EmQ4xfjTc1akr0iUFZNfxrXJ/R1T90h/ILm9iXEY6+iPrmYB3pXKjw==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-musl@4.44.0': - resolution: {integrity: sha512-PQUobbhLTQT5yz/SPg116VJBgz+XOtXt8D1ck+sfJJhuEsMj2jSej5yTdp8CvWBSceu+WW+ibVL6dm0ptG5fcA==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-win32-arm64-msvc@4.44.0': - resolution: {integrity: sha512-M0CpcHf8TWn+4oTxJfh7LQuTuaYeXGbk0eageVjQCKzYLsajWS/lFC94qlRqOlyC2KvRT90ZrfXULYmukeIy7w==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.44.0': - resolution: {integrity: sha512-3XJ0NQtMAXTWFW8FqZKcw3gOQwBtVWP/u8TpHP3CRPXD7Pd6s8lLdH3sHWh8vqKCyyiI8xW5ltJScQmBU9j7WA==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.44.0': - resolution: {integrity: sha512-Q2Mgwt+D8hd5FIPUuPDsvPR7Bguza6yTkJxspDGkZj7tBRn2y4KSWYuIXpftFSjBra76TbKerCV7rgFPQrn+wQ==} - cpu: [x64] - os: [win32] - - '@tybys/wasm-util@0.9.0': - resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} - - '@types/chai@5.2.2': - resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} - - '@types/debug@4.1.12': - resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} - - '@types/deep-eql@4.0.2': - resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - - '@types/ms@2.1.0': - resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - - '@types/node@22.15.32': - resolution: {integrity: sha512-3jigKqgSjsH6gYZv2nEsqdXfZqIFGAV36XYYjf9KGZ3PSG+IhLecqPnI310RvjutyMwifE2hhhNEklOUrvx/wA==} - - '@types/uuid@10.0.0': - resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} - - '@types/ws@8.18.1': - resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - - '@vitest/expect@3.2.4': - resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} - - '@vitest/mocker@3.2.4': - resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} - peerDependencies: - msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 - peerDependenciesMeta: - msw: - optional: true - vite: - optional: true - - '@vitest/pretty-format@3.2.4': - resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - - '@vitest/runner@3.2.4': - resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} - - '@vitest/snapshot@3.2.4': - resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} - - '@vitest/spy@3.2.4': - resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} - - '@vitest/utils@3.2.4': - resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} - - ansis@4.1.0: - resolution: {integrity: sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==} - engines: {node: '>=14'} - - args-tokenizer@0.3.0: - resolution: {integrity: sha512-xXAd7G2Mll5W8uo37GETpQ2VrE84M181Z7ugHFGQnJZ50M2mbOv0osSZ9VsSgPfJQ+LVG0prSi0th+ELMsno7Q==} - - assertion-error@2.0.1: - resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} - engines: {node: '>=12'} - - ast-kit@2.1.0: - resolution: {integrity: sha512-ROM2LlXbZBZVk97crfw8PGDOBzzsJvN2uJCmwswvPUNyfH14eg90mSN3xNqsri1JS1G9cz0VzeDUhxJkTrr4Ew==} - engines: {node: '>=20.18.0'} - - atomic-sleep@1.0.0: - resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} - engines: {node: '>=8.0.0'} - - birpc@2.4.0: - resolution: {integrity: sha512-5IdNxTyhXHv2UlgnPHQ0h+5ypVmkrYHzL8QT+DwFZ//2N/oNV8Ch+BCRmTJ3x6/z9Axo/cXYBc9eprsUVK/Jsg==} - - bumpp@10.2.0: - resolution: {integrity: sha512-1EJ2NG3M3WYJj4m+GtcxNH6Y7zMQ8q68USMoUGKjM6qFTVXSXCnTxcQSUDV7j4KjLVbk2uK6345Z+6RKOv0w5A==} - engines: {node: '>=18'} - hasBin: true - - c12@3.0.4: - resolution: {integrity: sha512-t5FaZTYbbCtvxuZq9xxIruYydrAGsJ+8UdP0pZzMiK2xl/gNiSOy0OxhLzHUEEb0m1QXYqfzfvyIFEmz/g9lqg==} - peerDependencies: - magicast: ^0.3.5 - peerDependenciesMeta: - magicast: - optional: true - - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - - chai@5.2.0: - resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} - engines: {node: '>=12'} - - check-error@2.1.1: - resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} - engines: {node: '>= 16'} - - chokidar@4.0.3: - resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} - engines: {node: '>= 14.16.0'} - - citty@0.1.6: - resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} - - confbox@0.2.2: - resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} - - consola@3.4.2: - resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} - engines: {node: ^14.18.0 || >=16.10.0} - - debug@4.4.1: - resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - deep-eql@5.0.2: - resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} - engines: {node: '>=6'} - - defu@6.1.4: - resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} - - destr@2.0.5: - resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} - - diff@8.0.2: - resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} - engines: {node: '>=0.3.1'} - - dotenv@16.5.0: - resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} - engines: {node: '>=12'} - - dts-resolver@2.1.1: - resolution: {integrity: sha512-3BiGFhB6mj5Kv+W2vdJseQUYW+SKVzAFJL6YNP6ursbrwy1fXHRotfHi3xLNxe4wZl/K8qbAFeCDjZLjzqxxRw==} - engines: {node: '>=20.18.0'} - peerDependencies: - oxc-resolver: '>=11.0.0' - peerDependenciesMeta: - oxc-resolver: - optional: true - - empathic@1.1.0: - resolution: {integrity: sha512-rsPft6CK3eHtrlp9Y5ALBb+hfK+DWnA4WFebbazxjWyx8vSm3rZeoM3z9irsjcqO3PYRzlfv27XIB4tz2DV7RA==} - engines: {node: '>=14'} - - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - - esbuild@0.25.5: - resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} - engines: {node: '>=18'} - hasBin: true - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - - expect-type@1.2.1: - resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} - engines: {node: '>=12.0.0'} - - exsolve@1.0.7: - resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} - - fast-redact@3.5.0: - resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} - engines: {node: '>=6'} - - fdir@6.4.6: - resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - get-tsconfig@4.10.1: - resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} - - giget@2.0.0: - resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} - hasBin: true - - happy-dom@17.6.3: - resolution: {integrity: sha512-UVIHeVhxmxedbWPCfgS55Jg2rDfwf2BCKeylcPSqazLz5w3Kri7Q4xdBJubsr/+VUzFLh0VjIvh13RaDA2/Xug==} - engines: {node: '>=20.0.0'} - - hookable@5.5.3: - resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} - - jiti@2.4.2: - resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} - hasBin: true - - js-tokens@9.0.1: - resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - - jsonc-parser@3.3.1: - resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} - - loupe@3.1.4: - resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==} - - magic-string@0.30.17: - resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - node-fetch-native@1.6.6: - resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==} - - nypm@0.6.0: - resolution: {integrity: sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg==} - engines: {node: ^14.16.0 || >=16.10.0} - hasBin: true - - ohash@2.0.11: - resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} - - on-exit-leak-free@2.1.2: - resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} - engines: {node: '>=14.0.0'} - - package-manager-detector@1.3.0: - resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} - - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - - pathval@2.0.0: - resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} - engines: {node: '>= 14.16'} - - perfect-debounce@1.0.0: - resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@4.0.2: - resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} - engines: {node: '>=12'} - - pino-abstract-transport@2.0.0: - resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} - - pino-std-serializers@7.0.0: - resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} - - pino@9.7.0: - resolution: {integrity: sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==} - hasBin: true - - pkg-types@2.1.0: - resolution: {integrity: sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==} - - postcss@8.5.6: - resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} - engines: {node: ^10 || ^12 || >=14} - - posthog-node@5.1.1: - resolution: {integrity: sha512-6VISkNdxO24ehXiDA4dugyCSIV7lpGVaEu5kn/dlAj+SJ1lgcDru9PQ8p/+GSXsXVxohd1t7kHL2JKc9NoGb0w==} - engines: {node: '>=20'} - - process-warning@5.0.0: - resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} - - quansync@0.2.10: - resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} - - quick-format-unescaped@4.0.4: - resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} - - rc9@2.1.2: - resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} - - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} - engines: {node: '>= 14.18.0'} - - real-require@0.2.0: - resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} - engines: {node: '>= 12.13.0'} - - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - - rolldown-plugin-dts@0.13.12: - resolution: {integrity: sha512-4QdQQfMOWNDLhmvoG3USAUpCm75dgwWk3IrK6SV9Fw2U5uwcSE7oQTqEcmsUGEBsNxZ58+gtM1oX38MMf0g5PA==} - engines: {node: '>=20.18.0'} - peerDependencies: - '@typescript/native-preview': '>=7.0.0-dev.20250601.1' - rolldown: ^1.0.0-beta.9 - typescript: ^5.0.0 - vue-tsc: ~2.2.0 - peerDependenciesMeta: - '@typescript/native-preview': - optional: true - typescript: - optional: true - vue-tsc: - optional: true - - rolldown@1.0.0-beta.9: - resolution: {integrity: sha512-ZgZky52n6iF0UainGKjptKGrOG4Con2S5sdc4C4y2Oj25D5PHAY8Y8E5f3M2TSd/zlhQs574JlMeTe3vREczSg==} - hasBin: true - peerDependencies: - '@oxc-project/runtime': 0.70.0 - peerDependenciesMeta: - '@oxc-project/runtime': - optional: true - - rollup@4.44.0: - resolution: {integrity: sha512-qHcdEzLCiktQIfwBq420pn2dP+30uzqYxv9ETm91wdt2R9AFcWfjNAmje4NWlnCIQ5RMTzVf0ZyisOKqHR6RwA==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - safe-stable-stringify@2.5.0: - resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} - engines: {node: '>=10'} - - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} - engines: {node: '>=10'} - hasBin: true - - siginfo@2.0.0: - resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - - sonic-boom@4.2.0: - resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - split2@4.2.0: - resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} - engines: {node: '>= 10.x'} - - stackback@0.0.2: - resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - - std-env@3.9.0: - resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} - - strip-literal@3.0.0: - resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} - - thread-stream@3.1.0: - resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} - - tinybench@2.9.0: - resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} - - tinyexec@1.0.1: - resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} - - tinyglobby@0.2.14: - resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} - engines: {node: '>=12.0.0'} - - tinypool@1.1.1: - resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} - engines: {node: ^18.0.0 || >=20.0.0} - - tinyrainbow@2.0.0: - resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} - engines: {node: '>=14.0.0'} - - tinyspy@4.0.3: - resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} - engines: {node: '>=14.0.0'} - - tsdown@0.11.13: - resolution: {integrity: sha512-VSfoNm8MJXFdg7PJ4p2javgjMRiQQHpkP9N3iBBTrmCixcT6YZ9ZtqYMW3NDHczqR0C0Qnur1HMQr1ZfZcmrng==} - engines: {node: '>=18.0.0'} - hasBin: true - peerDependencies: - publint: ^0.3.0 - typescript: ^5.0.0 - unplugin-lightningcss: ^0.4.0 - unplugin-unused: ^0.5.0 - peerDependenciesMeta: - publint: - optional: true - typescript: - optional: true - unplugin-lightningcss: - optional: true - unplugin-unused: - optional: true - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tsx@4.20.3: - resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==} - engines: {node: '>=18.0.0'} - hasBin: true - - typescript@5.8.3: - resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} - engines: {node: '>=14.17'} - hasBin: true - - unconfig@7.3.2: - resolution: {integrity: sha512-nqG5NNL2wFVGZ0NA/aCFw0oJ2pxSf1lwg4Z5ill8wd7K4KX/rQbHlwbh+bjctXL5Ly1xtzHenHGOK0b+lG6JVg==} - - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - - uuid@11.1.0: - resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} - hasBin: true - - vite-node@3.2.4: - resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - - vite@6.3.5: - resolution: {integrity: sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - jiti: '>=1.21.0' - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - jiti: - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - vitest@3.2.4: - resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.2.4 - '@vitest/ui': 3.2.4 - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@types/debug': - optional: true - '@types/node': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - happy-dom: - optional: true - jsdom: - optional: true - - webidl-conversions@7.0.0: - resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} - engines: {node: '>=12'} - - whatwg-mimetype@3.0.0: - resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} - engines: {node: '>=12'} - - why-is-node-running@2.3.0: - resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} - engines: {node: '>=8'} - hasBin: true - - yaml@2.8.0: - resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==} - engines: {node: '>= 14.6'} - hasBin: true - -snapshots: - - '@babel/generator@7.27.5': - dependencies: - '@babel/parser': 7.27.5 - '@babel/types': 7.27.6 - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 3.1.0 - - '@babel/helper-string-parser@7.27.1': {} - - '@babel/helper-validator-identifier@7.27.1': {} - - '@babel/parser@7.27.5': - dependencies: - '@babel/types': 7.27.6 - - '@babel/types@7.27.6': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - - '@biomejs/biome@1.9.4': - optionalDependencies: - '@biomejs/cli-darwin-arm64': 1.9.4 - '@biomejs/cli-darwin-x64': 1.9.4 - '@biomejs/cli-linux-arm64': 1.9.4 - '@biomejs/cli-linux-arm64-musl': 1.9.4 - '@biomejs/cli-linux-x64': 1.9.4 - '@biomejs/cli-linux-x64-musl': 1.9.4 - '@biomejs/cli-win32-arm64': 1.9.4 - '@biomejs/cli-win32-x64': 1.9.4 - - '@biomejs/cli-darwin-arm64@1.9.4': - optional: true - - '@biomejs/cli-darwin-x64@1.9.4': - optional: true - - '@biomejs/cli-linux-arm64-musl@1.9.4': - optional: true - - '@biomejs/cli-linux-arm64@1.9.4': - optional: true - - '@biomejs/cli-linux-x64-musl@1.9.4': - optional: true - - '@biomejs/cli-linux-x64@1.9.4': - optional: true - - '@biomejs/cli-win32-arm64@1.9.4': - optional: true - - '@biomejs/cli-win32-x64@1.9.4': - optional: true - - '@emnapi/core@1.4.3': - dependencies: - '@emnapi/wasi-threads': 1.0.2 - tslib: 2.8.1 - optional: true - - '@emnapi/runtime@1.4.3': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@1.0.2': - dependencies: - tslib: 2.8.1 - optional: true - - '@esbuild/aix-ppc64@0.25.5': - optional: true - - '@esbuild/android-arm64@0.25.5': - optional: true - - '@esbuild/android-arm@0.25.5': - optional: true - - '@esbuild/android-x64@0.25.5': - optional: true - - '@esbuild/darwin-arm64@0.25.5': - optional: true - - '@esbuild/darwin-x64@0.25.5': - optional: true - - '@esbuild/freebsd-arm64@0.25.5': - optional: true - - '@esbuild/freebsd-x64@0.25.5': - optional: true - - '@esbuild/linux-arm64@0.25.5': - optional: true - - '@esbuild/linux-arm@0.25.5': - optional: true - - '@esbuild/linux-ia32@0.25.5': - optional: true - - '@esbuild/linux-loong64@0.25.5': - optional: true - - '@esbuild/linux-mips64el@0.25.5': - optional: true - - '@esbuild/linux-ppc64@0.25.5': - optional: true - - '@esbuild/linux-riscv64@0.25.5': - optional: true - - '@esbuild/linux-s390x@0.25.5': - optional: true - - '@esbuild/linux-x64@0.25.5': - optional: true - - '@esbuild/netbsd-arm64@0.25.5': - optional: true - - '@esbuild/netbsd-x64@0.25.5': - optional: true - - '@esbuild/openbsd-arm64@0.25.5': - optional: true - - '@esbuild/openbsd-x64@0.25.5': - optional: true - - '@esbuild/sunos-x64@0.25.5': - optional: true - - '@esbuild/win32-arm64@0.25.5': - optional: true - - '@esbuild/win32-ia32@0.25.5': - optional: true - - '@esbuild/win32-x64@0.25.5': - optional: true - - '@jridgewell/gen-mapping@0.3.8': - dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/set-array@1.2.1': {} - - '@jridgewell/sourcemap-codec@1.5.0': {} - - '@jridgewell/trace-mapping@0.3.25': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 - - '@napi-rs/wasm-runtime@0.2.11': - dependencies: - '@emnapi/core': 1.4.3 - '@emnapi/runtime': 1.4.3 - '@tybys/wasm-util': 0.9.0 - optional: true - - '@oxc-project/types@0.70.0': {} - - '@quansync/fs@0.1.3': - dependencies: - quansync: 0.2.10 - - '@rolldown/binding-darwin-arm64@1.0.0-beta.9': - optional: true - - '@rolldown/binding-darwin-x64@1.0.0-beta.9': - optional: true - - '@rolldown/binding-freebsd-x64@1.0.0-beta.9': - optional: true - - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9': - optional: true - - '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9': - optional: true - - '@rolldown/binding-linux-arm64-musl@1.0.0-beta.9': - optional: true - - '@rolldown/binding-linux-x64-gnu@1.0.0-beta.9': - optional: true - - '@rolldown/binding-linux-x64-musl@1.0.0-beta.9': - optional: true - - '@rolldown/binding-wasm32-wasi@1.0.0-beta.9': - dependencies: - '@napi-rs/wasm-runtime': 0.2.11 - optional: true - - '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9': - optional: true - - '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9': - optional: true - - '@rolldown/binding-win32-x64-msvc@1.0.0-beta.9': - optional: true - - '@rolldown/pluginutils@1.0.0-beta.9': {} - - '@rollup/rollup-android-arm-eabi@4.44.0': - optional: true - - '@rollup/rollup-android-arm64@4.44.0': - optional: true - - '@rollup/rollup-darwin-arm64@4.44.0': - optional: true - - '@rollup/rollup-darwin-x64@4.44.0': - optional: true - - '@rollup/rollup-freebsd-arm64@4.44.0': - optional: true - - '@rollup/rollup-freebsd-x64@4.44.0': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.44.0': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.44.0': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.44.0': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.44.0': - optional: true - - '@rollup/rollup-linux-loongarch64-gnu@4.44.0': - optional: true - - '@rollup/rollup-linux-powerpc64le-gnu@4.44.0': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.44.0': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.44.0': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.44.0': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.44.0': - optional: true - - '@rollup/rollup-linux-x64-musl@4.44.0': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.44.0': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.44.0': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.44.0': - optional: true - - '@tybys/wasm-util@0.9.0': - dependencies: - tslib: 2.8.1 - optional: true - - '@types/chai@5.2.2': - dependencies: - '@types/deep-eql': 4.0.2 - - '@types/debug@4.1.12': - dependencies: - '@types/ms': 2.1.0 - optional: true - - '@types/deep-eql@4.0.2': {} - - '@types/estree@1.0.8': {} - - '@types/ms@2.1.0': - optional: true - - '@types/node@22.15.32': - dependencies: - undici-types: 6.21.0 - - '@types/uuid@10.0.0': {} - - '@types/ws@8.18.1': - dependencies: - '@types/node': 22.15.32 - - '@vitest/expect@3.2.4': - dependencies: - '@types/chai': 5.2.2 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.2.0 - tinyrainbow: 2.0.0 - - '@vitest/mocker@3.2.4(vite@6.3.5(@types/node@22.15.32)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0))': - dependencies: - '@vitest/spy': 3.2.4 - estree-walker: 3.0.3 - magic-string: 0.30.17 - optionalDependencies: - vite: 6.3.5(@types/node@22.15.32)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) - - '@vitest/pretty-format@3.2.4': - dependencies: - tinyrainbow: 2.0.0 - - '@vitest/runner@3.2.4': - dependencies: - '@vitest/utils': 3.2.4 - pathe: 2.0.3 - strip-literal: 3.0.0 - - '@vitest/snapshot@3.2.4': - dependencies: - '@vitest/pretty-format': 3.2.4 - magic-string: 0.30.17 - pathe: 2.0.3 - - '@vitest/spy@3.2.4': - dependencies: - tinyspy: 4.0.3 - - '@vitest/utils@3.2.4': - dependencies: - '@vitest/pretty-format': 3.2.4 - loupe: 3.1.4 - tinyrainbow: 2.0.0 - - ansis@4.1.0: {} - - args-tokenizer@0.3.0: {} - - assertion-error@2.0.1: {} - - ast-kit@2.1.0: - dependencies: - '@babel/parser': 7.27.5 - pathe: 2.0.3 - - atomic-sleep@1.0.0: {} - - birpc@2.4.0: {} - - bumpp@10.2.0: - dependencies: - ansis: 4.1.0 - args-tokenizer: 0.3.0 - c12: 3.0.4 - cac: 6.7.14 - escalade: 3.2.0 - jsonc-parser: 3.3.1 - package-manager-detector: 1.3.0 - semver: 7.7.2 - tinyexec: 1.0.1 - tinyglobby: 0.2.14 - yaml: 2.8.0 - transitivePeerDependencies: - - magicast - - c12@3.0.4: - dependencies: - chokidar: 4.0.3 - confbox: 0.2.2 - defu: 6.1.4 - dotenv: 16.5.0 - exsolve: 1.0.7 - giget: 2.0.0 - jiti: 2.4.2 - ohash: 2.0.11 - pathe: 2.0.3 - perfect-debounce: 1.0.0 - pkg-types: 2.1.0 - rc9: 2.1.2 - - cac@6.7.14: {} - - chai@5.2.0: - dependencies: - assertion-error: 2.0.1 - check-error: 2.1.1 - deep-eql: 5.0.2 - loupe: 3.1.4 - pathval: 2.0.0 - - check-error@2.1.1: {} - - chokidar@4.0.3: - dependencies: - readdirp: 4.1.2 - - citty@0.1.6: - dependencies: - consola: 3.4.2 - - confbox@0.2.2: {} - - consola@3.4.2: {} - - debug@4.4.1: - dependencies: - ms: 2.1.3 - - deep-eql@5.0.2: {} - - defu@6.1.4: {} - - destr@2.0.5: {} - - diff@8.0.2: {} - - dotenv@16.5.0: {} - - dts-resolver@2.1.1: {} - - empathic@1.1.0: {} - - es-module-lexer@1.7.0: {} - - esbuild@0.25.5: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.5 - '@esbuild/android-arm': 0.25.5 - '@esbuild/android-arm64': 0.25.5 - '@esbuild/android-x64': 0.25.5 - '@esbuild/darwin-arm64': 0.25.5 - '@esbuild/darwin-x64': 0.25.5 - '@esbuild/freebsd-arm64': 0.25.5 - '@esbuild/freebsd-x64': 0.25.5 - '@esbuild/linux-arm': 0.25.5 - '@esbuild/linux-arm64': 0.25.5 - '@esbuild/linux-ia32': 0.25.5 - '@esbuild/linux-loong64': 0.25.5 - '@esbuild/linux-mips64el': 0.25.5 - '@esbuild/linux-ppc64': 0.25.5 - '@esbuild/linux-riscv64': 0.25.5 - '@esbuild/linux-s390x': 0.25.5 - '@esbuild/linux-x64': 0.25.5 - '@esbuild/netbsd-arm64': 0.25.5 - '@esbuild/netbsd-x64': 0.25.5 - '@esbuild/openbsd-arm64': 0.25.5 - '@esbuild/openbsd-x64': 0.25.5 - '@esbuild/sunos-x64': 0.25.5 - '@esbuild/win32-arm64': 0.25.5 - '@esbuild/win32-ia32': 0.25.5 - '@esbuild/win32-x64': 0.25.5 - - escalade@3.2.0: {} - - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.8 - - expect-type@1.2.1: {} - - exsolve@1.0.7: {} - - fast-redact@3.5.0: {} - - fdir@6.4.6(picomatch@4.0.2): - optionalDependencies: - picomatch: 4.0.2 - - fsevents@2.3.3: - optional: true - - get-tsconfig@4.10.1: - dependencies: - resolve-pkg-maps: 1.0.0 - - giget@2.0.0: - dependencies: - citty: 0.1.6 - consola: 3.4.2 - defu: 6.1.4 - node-fetch-native: 1.6.6 - nypm: 0.6.0 - pathe: 2.0.3 - - happy-dom@17.6.3: - dependencies: - webidl-conversions: 7.0.0 - whatwg-mimetype: 3.0.0 - - hookable@5.5.3: {} - - jiti@2.4.2: {} - - js-tokens@9.0.1: {} - - jsesc@3.1.0: {} - - jsonc-parser@3.3.1: {} - - loupe@3.1.4: {} - - magic-string@0.30.17: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 - - ms@2.1.3: {} - - nanoid@3.3.11: {} - - node-fetch-native@1.6.6: {} - - nypm@0.6.0: - dependencies: - citty: 0.1.6 - consola: 3.4.2 - pathe: 2.0.3 - pkg-types: 2.1.0 - tinyexec: 0.3.2 - - ohash@2.0.11: {} - - on-exit-leak-free@2.1.2: {} - - package-manager-detector@1.3.0: {} - - pathe@2.0.3: {} - - pathval@2.0.0: {} - - perfect-debounce@1.0.0: {} - - picocolors@1.1.1: {} - - picomatch@4.0.2: {} - - pino-abstract-transport@2.0.0: - dependencies: - split2: 4.2.0 - - pino-std-serializers@7.0.0: {} - - pino@9.7.0: - dependencies: - atomic-sleep: 1.0.0 - fast-redact: 3.5.0 - on-exit-leak-free: 2.1.2 - pino-abstract-transport: 2.0.0 - pino-std-serializers: 7.0.0 - process-warning: 5.0.0 - quick-format-unescaped: 4.0.4 - real-require: 0.2.0 - safe-stable-stringify: 2.5.0 - sonic-boom: 4.2.0 - thread-stream: 3.1.0 - - pkg-types@2.1.0: - dependencies: - confbox: 0.2.2 - exsolve: 1.0.7 - pathe: 2.0.3 - - postcss@8.5.6: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - posthog-node@5.1.1: {} - - process-warning@5.0.0: {} - - quansync@0.2.10: {} - - quick-format-unescaped@4.0.4: {} - - rc9@2.1.2: - dependencies: - defu: 6.1.4 - destr: 2.0.5 - - readdirp@4.1.2: {} - - real-require@0.2.0: {} - - resolve-pkg-maps@1.0.0: {} - - rolldown-plugin-dts@0.13.12(rolldown@1.0.0-beta.9)(typescript@5.8.3): - dependencies: - '@babel/generator': 7.27.5 - '@babel/parser': 7.27.5 - '@babel/types': 7.27.6 - ast-kit: 2.1.0 - birpc: 2.4.0 - debug: 4.4.1 - dts-resolver: 2.1.1 - get-tsconfig: 4.10.1 - rolldown: 1.0.0-beta.9 - optionalDependencies: - typescript: 5.8.3 - transitivePeerDependencies: - - oxc-resolver - - supports-color - - rolldown@1.0.0-beta.9: - dependencies: - '@oxc-project/types': 0.70.0 - '@rolldown/pluginutils': 1.0.0-beta.9 - ansis: 4.1.0 - optionalDependencies: - '@rolldown/binding-darwin-arm64': 1.0.0-beta.9 - '@rolldown/binding-darwin-x64': 1.0.0-beta.9 - '@rolldown/binding-freebsd-x64': 1.0.0-beta.9 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.9 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.9 - '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.9 - '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.9 - '@rolldown/binding-linux-x64-musl': 1.0.0-beta.9 - '@rolldown/binding-wasm32-wasi': 1.0.0-beta.9 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.9 - '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.9 - '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.9 - - rollup@4.44.0: - dependencies: - '@types/estree': 1.0.8 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.44.0 - '@rollup/rollup-android-arm64': 4.44.0 - '@rollup/rollup-darwin-arm64': 4.44.0 - '@rollup/rollup-darwin-x64': 4.44.0 - '@rollup/rollup-freebsd-arm64': 4.44.0 - '@rollup/rollup-freebsd-x64': 4.44.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.44.0 - '@rollup/rollup-linux-arm-musleabihf': 4.44.0 - '@rollup/rollup-linux-arm64-gnu': 4.44.0 - '@rollup/rollup-linux-arm64-musl': 4.44.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.44.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.44.0 - '@rollup/rollup-linux-riscv64-gnu': 4.44.0 - '@rollup/rollup-linux-riscv64-musl': 4.44.0 - '@rollup/rollup-linux-s390x-gnu': 4.44.0 - '@rollup/rollup-linux-x64-gnu': 4.44.0 - '@rollup/rollup-linux-x64-musl': 4.44.0 - '@rollup/rollup-win32-arm64-msvc': 4.44.0 - '@rollup/rollup-win32-ia32-msvc': 4.44.0 - '@rollup/rollup-win32-x64-msvc': 4.44.0 - fsevents: 2.3.3 - - safe-stable-stringify@2.5.0: {} - - semver@7.7.2: {} - - siginfo@2.0.0: {} - - sonic-boom@4.2.0: - dependencies: - atomic-sleep: 1.0.0 - - source-map-js@1.2.1: {} - - split2@4.2.0: {} - - stackback@0.0.2: {} - - std-env@3.9.0: {} - - strip-literal@3.0.0: - dependencies: - js-tokens: 9.0.1 - - thread-stream@3.1.0: - dependencies: - real-require: 0.2.0 - - tinybench@2.9.0: {} - - tinyexec@0.3.2: {} - - tinyexec@1.0.1: {} - - tinyglobby@0.2.14: - dependencies: - fdir: 6.4.6(picomatch@4.0.2) - picomatch: 4.0.2 - - tinypool@1.1.1: {} - - tinyrainbow@2.0.0: {} - - tinyspy@4.0.3: {} - - tsdown@0.11.13(typescript@5.8.3): - dependencies: - ansis: 4.1.0 - cac: 6.7.14 - chokidar: 4.0.3 - debug: 4.4.1 - diff: 8.0.2 - empathic: 1.1.0 - hookable: 5.5.3 - rolldown: 1.0.0-beta.9 - rolldown-plugin-dts: 0.13.12(rolldown@1.0.0-beta.9)(typescript@5.8.3) - semver: 7.7.2 - tinyexec: 1.0.1 - tinyglobby: 0.2.14 - unconfig: 7.3.2 - optionalDependencies: - typescript: 5.8.3 - transitivePeerDependencies: - - '@oxc-project/runtime' - - '@typescript/native-preview' - - oxc-resolver - - supports-color - - vue-tsc - - tslib@2.8.1: - optional: true - - tsx@4.20.3: - dependencies: - esbuild: 0.25.5 - get-tsconfig: 4.10.1 - optionalDependencies: - fsevents: 2.3.3 - - typescript@5.8.3: {} - - unconfig@7.3.2: - dependencies: - '@quansync/fs': 0.1.3 - defu: 6.1.4 - jiti: 2.4.2 - quansync: 0.2.10 - - undici-types@6.21.0: {} - - uuid@11.1.0: {} - - vite-node@3.2.4(@types/node@22.15.32)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0): - dependencies: - cac: 6.7.14 - debug: 4.4.1 - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 6.3.5(@types/node@22.15.32)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - vite@6.3.5(@types/node@22.15.32)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0): - dependencies: - esbuild: 0.25.5 - fdir: 6.4.6(picomatch@4.0.2) - picomatch: 4.0.2 - postcss: 8.5.6 - rollup: 4.44.0 - tinyglobby: 0.2.14 - optionalDependencies: - '@types/node': 22.15.32 - fsevents: 2.3.3 - jiti: 2.4.2 - tsx: 4.20.3 - yaml: 2.8.0 - - vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.32)(happy-dom@17.6.3)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0): - dependencies: - '@types/chai': 5.2.2 - '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@22.15.32)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0)) - '@vitest/pretty-format': 3.2.4 - '@vitest/runner': 3.2.4 - '@vitest/snapshot': 3.2.4 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.2.0 - debug: 4.4.1 - expect-type: 1.2.1 - magic-string: 0.30.17 - pathe: 2.0.3 - picomatch: 4.0.2 - std-env: 3.9.0 - tinybench: 2.9.0 - tinyexec: 0.3.2 - tinyglobby: 0.2.14 - tinypool: 1.1.1 - tinyrainbow: 2.0.0 - vite: 6.3.5(@types/node@22.15.32)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) - vite-node: 3.2.4(@types/node@22.15.32)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/debug': 4.1.12 - '@types/node': 22.15.32 - happy-dom: 17.6.3 - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - webidl-conversions@7.0.0: {} - - whatwg-mimetype@3.0.0: {} - - why-is-node-running@2.3.0: - dependencies: - siginfo: 2.0.0 - stackback: 0.0.2 - - yaml@2.8.0: {} diff --git a/libs/typescript/package.json b/libs/typescript/package.json index ff69852c..1b054bc6 100644 --- a/libs/typescript/package.json +++ b/libs/typescript/package.json @@ -7,7 +7,14 @@ "license": "MIT", "scripts": { "lint": "biome check", - "lint:fix": "biome check --fix" + "lint:fix": "biome check --fix", + "build:core": "pnpm --filter @cua/core build", + "build:computer": "pnpm --filter @cua/computer build", + "build": "pnpm build:core && pnpm build:computer", + "test:core": "pnpm --filter @cua/core test", + "test:computer": "pnpm --filter @cua/computer test", + "test": "pnpm -r test", + "typecheck": "pnpm -r typecheck" }, "packageManager": "pnpm@10.6.5", "devDependencies": { @@ -15,7 +22,8 @@ }, "pnpm": { "onlyBuiltDependencies": [ - "@biomejs/biome" + "@biomejs/biome", + "esbuild" ] } } \ No newline at end of file diff --git a/libs/typescript/pnpm-lock.yaml b/libs/typescript/pnpm-lock.yaml index 26c84528..c2f97fe7 100644 --- a/libs/typescript/pnpm-lock.yaml +++ b/libs/typescript/pnpm-lock.yaml @@ -12,8 +12,112 @@ importers: specifier: ^1.9.4 version: 1.9.4 + computer: + dependencies: + '@cua/core': + specifier: link:../core + version: link:../core + pino: + specifier: ^9.7.0 + version: 9.7.0 + ws: + specifier: ^8.18.0 + version: 8.18.2 + devDependencies: + '@biomejs/biome': + specifier: ^1.9.4 + version: 1.9.4 + '@types/node': + specifier: ^22.15.17 + version: 22.15.33 + '@types/ws': + specifier: ^8.18.1 + version: 8.18.1 + bumpp: + specifier: ^10.1.0 + version: 10.2.0 + happy-dom: + specifier: ^17.4.7 + version: 17.6.3 + tsdown: + specifier: ^0.11.9 + version: 0.11.13(typescript@5.8.3) + tsx: + specifier: ^4.19.4 + version: 4.20.3 + typescript: + specifier: ^5.8.3 + version: 5.8.3 + vitest: + specifier: ^3.1.3 + version: 3.2.4(@types/node@22.15.33)(happy-dom@17.6.3)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + + core: + dependencies: + '@types/uuid': + specifier: ^10.0.0 + version: 10.0.0 + pino: + specifier: ^9.7.0 + version: 9.7.0 + posthog-node: + specifier: ^5.1.1 + version: 5.1.1 + uuid: + specifier: ^11.1.0 + version: 11.1.0 + devDependencies: + '@biomejs/biome': + specifier: ^1.9.4 + version: 1.9.4 + '@types/node': + specifier: ^22.15.17 + version: 22.15.33 + '@types/ws': + specifier: ^8.18.1 + version: 8.18.1 + bumpp: + specifier: ^10.1.0 + version: 10.2.0 + happy-dom: + specifier: ^17.4.7 + version: 17.6.3 + tsdown: + specifier: ^0.11.9 + version: 0.11.13(typescript@5.8.3) + tsx: + specifier: ^4.19.4 + version: 4.20.3 + typescript: + specifier: ^5.8.3 + version: 5.8.3 + vitest: + specifier: ^3.1.3 + version: 3.2.4(@types/node@22.15.33)(happy-dom@17.6.3)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + packages: + '@babel/generator@7.27.5': + resolution: {integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.27.5': + resolution: {integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.27.6': + resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==} + engines: {node: '>=6.9.0'} + '@biomejs/biome@1.9.4': resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} engines: {node: '>=14.21.3'} @@ -67,8 +171,926 @@ packages: cpu: [x64] os: [win32] + '@emnapi/core@1.4.3': + resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} + + '@emnapi/runtime@1.4.3': + resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} + + '@emnapi/wasi-threads@1.0.2': + resolution: {integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==} + + '@esbuild/aix-ppc64@0.25.5': + resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.5': + resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.5': + resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.5': + resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.5': + resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.5': + resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.5': + resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.5': + resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.5': + resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.5': + resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.5': + resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.5': + resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.5': + resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.5': + resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.5': + resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.5': + resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.5': + resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.5': + resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.5': + resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.5': + resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.5': + resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.25.5': + resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.5': + resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.5': + resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.5': + resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@napi-rs/wasm-runtime@0.2.11': + resolution: {integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==} + + '@oxc-project/types@0.70.0': + resolution: {integrity: sha512-ngyLUpUjO3dpqygSRQDx7nMx8+BmXbWOU4oIwTJFV2MVIDG7knIZwgdwXlQWLg3C3oxg1lS7ppMtPKqKFb7wzw==} + + '@quansync/fs@0.1.3': + resolution: {integrity: sha512-G0OnZbMWEs5LhDyqy2UL17vGhSVHkQIfVojMtEWVenvj0V5S84VBgy86kJIuNsGDp2p7sTKlpSIpBUWdC35OKg==} + engines: {node: '>=20.0.0'} + + '@rolldown/binding-darwin-arm64@1.0.0-beta.9': + resolution: {integrity: sha512-geUG/FUpm+membLC0NQBb39vVyOfguYZ2oyXc7emr6UjH6TeEECT4b0CPZXKFnELareTiU/Jfl70/eEgNxyQeA==} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-beta.9': + resolution: {integrity: sha512-7wPXDwcOtv2I+pWTL2UNpNAxMAGukgBT90Jz4DCfwaYdGvQncF7J0S7IWrRVsRFhBavxM+65RcueE3VXw5UIbg==} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-beta.9': + resolution: {integrity: sha512-agO5mONTNKVrcIt4SRxw5Ni0FOVV3gaH8dIiNp1A4JeU91b9kw7x+JRuNJAQuM2X3pYqVvA6qh13UTNOsaqM/Q==} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9': + resolution: {integrity: sha512-dDNDV9p/8WYDriS9HCcbH6y6+JP38o3enj/pMkdkmkxEnZ0ZoHIfQ9RGYWeRYU56NKBCrya4qZBJx49Jk9LRug==} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9': + resolution: {integrity: sha512-kZKegmHG1ZvfsFIwYU6DeFSxSIcIliXzeznsJHUo9D9/dlVSDi/PUvsRKcuJkQjZoejM6pk8MHN/UfgGdIhPHw==} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.9': + resolution: {integrity: sha512-f+VL8mO31pyMJiJPr2aA1ryYONkP2UqgbwK7fKtKHZIeDd/AoUGn3+ujPqDhuy2NxgcJ5H8NaSvDpG1tJMHh+g==} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.9': + resolution: {integrity: sha512-GiUEZ0WPjX5LouDoC3O8aJa4h6BLCpIvaAboNw5JoRour/3dC6rbtZZ/B5FC3/ySsN3/dFOhAH97ylQxoZJi7A==} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-musl@1.0.0-beta.9': + resolution: {integrity: sha512-AMb0dicw+QHh6RxvWo4BRcuTMgS0cwUejJRMpSyIcHYnKTbj6nUW4HbWNQuDfZiF27l6F5gEwBS+YLUdVzL9vg==} + cpu: [x64] + os: [linux] + + '@rolldown/binding-wasm32-wasi@1.0.0-beta.9': + resolution: {integrity: sha512-+pdaiTx7L8bWKvsAuCE0HAxP1ze1WOLoWGCawcrZbMSY10dMh2i82lJiH6tXGXbfYYwsNWhWE2NyG4peFZvRfQ==} + engines: {node: '>=14.21.3'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9': + resolution: {integrity: sha512-A7kN248viWvb8eZMzQu024TBKGoyoVYBsDG2DtoP8u2pzwoh5yDqUL291u01o4f8uzpUHq8mfwQJmcGChFu8KQ==} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9': + resolution: {integrity: sha512-DzKN7iEYjAP8AK8F2G2aCej3fk43Y/EQrVrR3gF0XREes56chjQ7bXIhw819jv74BbxGdnpPcslhet/cgt7WRA==} + cpu: [ia32] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.9': + resolution: {integrity: sha512-GMWgTvvbZ8TfBsAiJpoz4SRq3IN3aUMn0rYm8q4I8dcEk4J1uISyfb6ZMzvqW+cvScTWVKWZNqnrmYOKLLUt4w==} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.0-beta.9': + resolution: {integrity: sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==} + + '@rollup/rollup-android-arm-eabi@4.44.0': + resolution: {integrity: sha512-xEiEE5oDW6tK4jXCAyliuntGR+amEMO7HLtdSshVuhFnKTYoeYMyXQK7pLouAJJj5KHdwdn87bfHAR2nSdNAUA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.44.0': + resolution: {integrity: sha512-uNSk/TgvMbskcHxXYHzqwiyBlJ/lGcv8DaUfcnNwict8ba9GTTNxfn3/FAoFZYgkaXXAdrAA+SLyKplyi349Jw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.44.0': + resolution: {integrity: sha512-VGF3wy0Eq1gcEIkSCr8Ke03CWT+Pm2yveKLaDvq51pPpZza3JX/ClxXOCmTYYq3us5MvEuNRTaeyFThCKRQhOA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.44.0': + resolution: {integrity: sha512-fBkyrDhwquRvrTxSGH/qqt3/T0w5Rg0L7ZIDypvBPc1/gzjJle6acCpZ36blwuwcKD/u6oCE/sRWlUAcxLWQbQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.44.0': + resolution: {integrity: sha512-u5AZzdQJYJXByB8giQ+r4VyfZP+walV+xHWdaFx/1VxsOn6eWJhK2Vl2eElvDJFKQBo/hcYIBg/jaKS8ZmKeNQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.44.0': + resolution: {integrity: sha512-qC0kS48c/s3EtdArkimctY7h3nHicQeEUdjJzYVJYR3ct3kWSafmn6jkNCA8InbUdge6PVx6keqjk5lVGJf99g==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.44.0': + resolution: {integrity: sha512-x+e/Z9H0RAWckn4V2OZZl6EmV0L2diuX3QB0uM1r6BvhUIv6xBPL5mrAX2E3e8N8rEHVPwFfz/ETUbV4oW9+lQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.44.0': + resolution: {integrity: sha512-1exwiBFf4PU/8HvI8s80icyCcnAIB86MCBdst51fwFmH5dyeoWVPVgmQPcKrMtBQ0W5pAs7jBCWuRXgEpRzSCg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.44.0': + resolution: {integrity: sha512-ZTR2mxBHb4tK4wGf9b8SYg0Y6KQPjGpR4UWwTFdnmjB4qRtoATZ5dWn3KsDwGa5Z2ZBOE7K52L36J9LueKBdOQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.44.0': + resolution: {integrity: sha512-GFWfAhVhWGd4r6UxmnKRTBwP1qmModHtd5gkraeW2G490BpFOZkFtem8yuX2NyafIP/mGpRJgTJ2PwohQkUY/Q==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.44.0': + resolution: {integrity: sha512-xw+FTGcov/ejdusVOqKgMGW3c4+AgqrfvzWEVXcNP6zq2ue+lsYUgJ+5Rtn/OTJf7e2CbgTFvzLW2j0YAtj0Gg==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.44.0': + resolution: {integrity: sha512-bKGibTr9IdF0zr21kMvkZT4K6NV+jjRnBoVMt2uNMG0BYWm3qOVmYnXKzx7UhwrviKnmK46IKMByMgvpdQlyJQ==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.44.0': + resolution: {integrity: sha512-vV3cL48U5kDaKZtXrti12YRa7TyxgKAIDoYdqSIOMOFBXqFj2XbChHAtXquEn2+n78ciFgr4KIqEbydEGPxXgA==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.44.0': + resolution: {integrity: sha512-TDKO8KlHJuvTEdfw5YYFBjhFts2TR0VpZsnLLSYmB7AaohJhM8ctDSdDnUGq77hUh4m/djRafw+9zQpkOanE2Q==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.44.0': + resolution: {integrity: sha512-8541GEyktXaw4lvnGp9m84KENcxInhAt6vPWJ9RodsB/iGjHoMB2Pp5MVBCiKIRxrxzJhGCxmNzdu+oDQ7kwRA==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.44.0': + resolution: {integrity: sha512-iUVJc3c0o8l9Sa/qlDL2Z9UP92UZZW1+EmQ4xfjTc1akr0iUFZNfxrXJ/R1T90h/ILm9iXEY6+iPrmYB3pXKjw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.44.0': + resolution: {integrity: sha512-PQUobbhLTQT5yz/SPg116VJBgz+XOtXt8D1ck+sfJJhuEsMj2jSej5yTdp8CvWBSceu+WW+ibVL6dm0ptG5fcA==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.44.0': + resolution: {integrity: sha512-M0CpcHf8TWn+4oTxJfh7LQuTuaYeXGbk0eageVjQCKzYLsajWS/lFC94qlRqOlyC2KvRT90ZrfXULYmukeIy7w==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.44.0': + resolution: {integrity: sha512-3XJ0NQtMAXTWFW8FqZKcw3gOQwBtVWP/u8TpHP3CRPXD7Pd6s8lLdH3sHWh8vqKCyyiI8xW5ltJScQmBU9j7WA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.44.0': + resolution: {integrity: sha512-Q2Mgwt+D8hd5FIPUuPDsvPR7Bguza6yTkJxspDGkZj7tBRn2y4KSWYuIXpftFSjBra76TbKerCV7rgFPQrn+wQ==} + cpu: [x64] + os: [win32] + + '@tybys/wasm-util@0.9.0': + resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} + + '@types/chai@5.2.2': + resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/node@22.15.33': + resolution: {integrity: sha512-wzoocdnnpSxZ+6CjW4ADCK1jVmd1S/J3ArNWfn8FDDQtRm8dkDg7TA+mvek2wNrfCgwuZxqEOiB9B1XCJ6+dbw==} + + '@types/uuid@10.0.0': + resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + + ansis@4.1.0: + resolution: {integrity: sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==} + engines: {node: '>=14'} + + args-tokenizer@0.3.0: + resolution: {integrity: sha512-xXAd7G2Mll5W8uo37GETpQ2VrE84M181Z7ugHFGQnJZ50M2mbOv0osSZ9VsSgPfJQ+LVG0prSi0th+ELMsno7Q==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-kit@2.1.0: + resolution: {integrity: sha512-ROM2LlXbZBZVk97crfw8PGDOBzzsJvN2uJCmwswvPUNyfH14eg90mSN3xNqsri1JS1G9cz0VzeDUhxJkTrr4Ew==} + engines: {node: '>=20.18.0'} + + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + + birpc@2.4.0: + resolution: {integrity: sha512-5IdNxTyhXHv2UlgnPHQ0h+5ypVmkrYHzL8QT+DwFZ//2N/oNV8Ch+BCRmTJ3x6/z9Axo/cXYBc9eprsUVK/Jsg==} + + bumpp@10.2.0: + resolution: {integrity: sha512-1EJ2NG3M3WYJj4m+GtcxNH6Y7zMQ8q68USMoUGKjM6qFTVXSXCnTxcQSUDV7j4KjLVbk2uK6345Z+6RKOv0w5A==} + engines: {node: '>=18'} + hasBin: true + + c12@3.0.4: + resolution: {integrity: sha512-t5FaZTYbbCtvxuZq9xxIruYydrAGsJ+8UdP0pZzMiK2xl/gNiSOy0OxhLzHUEEb0m1QXYqfzfvyIFEmz/g9lqg==} + peerDependencies: + magicast: ^0.3.5 + peerDependenciesMeta: + magicast: + optional: true + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + chai@5.2.0: + resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==} + engines: {node: '>=12'} + + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + citty@0.1.6: + resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + + diff@8.0.2: + resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} + engines: {node: '>=0.3.1'} + + dotenv@16.5.0: + resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} + engines: {node: '>=12'} + + dts-resolver@2.1.1: + resolution: {integrity: sha512-3BiGFhB6mj5Kv+W2vdJseQUYW+SKVzAFJL6YNP6ursbrwy1fXHRotfHi3xLNxe4wZl/K8qbAFeCDjZLjzqxxRw==} + engines: {node: '>=20.18.0'} + peerDependencies: + oxc-resolver: '>=11.0.0' + peerDependenciesMeta: + oxc-resolver: + optional: true + + empathic@1.1.0: + resolution: {integrity: sha512-rsPft6CK3eHtrlp9Y5ALBb+hfK+DWnA4WFebbazxjWyx8vSm3rZeoM3z9irsjcqO3PYRzlfv27XIB4tz2DV7RA==} + engines: {node: '>=14'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + esbuild@0.25.5: + resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.2.1: + resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} + engines: {node: '>=12.0.0'} + + exsolve@1.0.7: + resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} + + fast-redact@3.5.0: + resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} + engines: {node: '>=6'} + + fdir@6.4.6: + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-tsconfig@4.10.1: + resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + + giget@2.0.0: + resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} + hasBin: true + + happy-dom@17.6.3: + resolution: {integrity: sha512-UVIHeVhxmxedbWPCfgS55Jg2rDfwf2BCKeylcPSqazLz5w3Kri7Q4xdBJubsr/+VUzFLh0VjIvh13RaDA2/Xug==} + engines: {node: '>=20.0.0'} + + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + + jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + hasBin: true + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + + loupe@3.1.4: + resolution: {integrity: sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==} + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-fetch-native@1.6.6: + resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==} + + nypm@0.6.0: + resolution: {integrity: sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg==} + engines: {node: ^14.16.0 || >=16.10.0} + hasBin: true + + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + + package-manager-detector@1.3.0: + resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + + pino-abstract-transport@2.0.0: + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} + + pino-std-serializers@7.0.0: + resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} + + pino@9.7.0: + resolution: {integrity: sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==} + hasBin: true + + pkg-types@2.1.0: + resolution: {integrity: sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + posthog-node@5.1.1: + resolution: {integrity: sha512-6VISkNdxO24ehXiDA4dugyCSIV7lpGVaEu5kn/dlAj+SJ1lgcDru9PQ8p/+GSXsXVxohd1t7kHL2JKc9NoGb0w==} + engines: {node: '>=20'} + + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + + quansync@0.2.10: + resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} + + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + + rc9@2.1.2: + resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + rolldown-plugin-dts@0.13.12: + resolution: {integrity: sha512-4QdQQfMOWNDLhmvoG3USAUpCm75dgwWk3IrK6SV9Fw2U5uwcSE7oQTqEcmsUGEBsNxZ58+gtM1oX38MMf0g5PA==} + engines: {node: '>=20.18.0'} + peerDependencies: + '@typescript/native-preview': '>=7.0.0-dev.20250601.1' + rolldown: ^1.0.0-beta.9 + typescript: ^5.0.0 + vue-tsc: ~2.2.0 + peerDependenciesMeta: + '@typescript/native-preview': + optional: true + typescript: + optional: true + vue-tsc: + optional: true + + rolldown@1.0.0-beta.9: + resolution: {integrity: sha512-ZgZky52n6iF0UainGKjptKGrOG4Con2S5sdc4C4y2Oj25D5PHAY8Y8E5f3M2TSd/zlhQs574JlMeTe3vREczSg==} + hasBin: true + peerDependencies: + '@oxc-project/runtime': 0.70.0 + peerDependenciesMeta: + '@oxc-project/runtime': + optional: true + + rollup@4.44.0: + resolution: {integrity: sha512-qHcdEzLCiktQIfwBq420pn2dP+30uzqYxv9ETm91wdt2R9AFcWfjNAmje4NWlnCIQ5RMTzVf0ZyisOKqHR6RwA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + sonic-boom@4.2.0: + resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.9.0: + resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + + strip-literal@3.0.0: + resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} + + thread-stream@3.1.0: + resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyexec@1.0.1: + resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} + + tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.3: + resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} + engines: {node: '>=14.0.0'} + + tsdown@0.11.13: + resolution: {integrity: sha512-VSfoNm8MJXFdg7PJ4p2javgjMRiQQHpkP9N3iBBTrmCixcT6YZ9ZtqYMW3NDHczqR0C0Qnur1HMQr1ZfZcmrng==} + engines: {node: '>=18.0.0'} + hasBin: true + peerDependencies: + publint: ^0.3.0 + typescript: ^5.0.0 + unplugin-lightningcss: ^0.4.0 + unplugin-unused: ^0.5.0 + peerDependenciesMeta: + publint: + optional: true + typescript: + optional: true + unplugin-lightningcss: + optional: true + unplugin-unused: + optional: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.20.3: + resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==} + engines: {node: '>=18.0.0'} + hasBin: true + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + unconfig@7.3.2: + resolution: {integrity: sha512-nqG5NNL2wFVGZ0NA/aCFw0oJ2pxSf1lwg4Z5ill8wd7K4KX/rQbHlwbh+bjctXL5Ly1xtzHenHGOK0b+lG6JVg==} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite@7.0.0: + resolution: {integrity: sha512-ixXJB1YRgDIw2OszKQS9WxGHKwLdCsbQNkpJN171udl6szi/rIySHL6/Os3s2+oE4P/FLD4dxg4mD7Wust+u5g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + 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 + + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-mimetype@3.0.0: + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + ws@8.18.2: + resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + yaml@2.8.0: + resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==} + engines: {node: '>= 14.6'} + hasBin: true + snapshots: + '@babel/generator@7.27.5': + dependencies: + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.1.0 + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/parser@7.27.5': + dependencies: + '@babel/types': 7.27.6 + + '@babel/types@7.27.6': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@biomejs/biome@1.9.4': optionalDependencies: '@biomejs/cli-darwin-arm64': 1.9.4 @@ -103,3 +1125,784 @@ snapshots: '@biomejs/cli-win32-x64@1.9.4': optional: true + + '@emnapi/core@1.4.3': + dependencies: + '@emnapi/wasi-threads': 1.0.2 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.4.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.0.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.25.5': + optional: true + + '@esbuild/android-arm64@0.25.5': + optional: true + + '@esbuild/android-arm@0.25.5': + optional: true + + '@esbuild/android-x64@0.25.5': + optional: true + + '@esbuild/darwin-arm64@0.25.5': + optional: true + + '@esbuild/darwin-x64@0.25.5': + optional: true + + '@esbuild/freebsd-arm64@0.25.5': + optional: true + + '@esbuild/freebsd-x64@0.25.5': + optional: true + + '@esbuild/linux-arm64@0.25.5': + optional: true + + '@esbuild/linux-arm@0.25.5': + optional: true + + '@esbuild/linux-ia32@0.25.5': + optional: true + + '@esbuild/linux-loong64@0.25.5': + optional: true + + '@esbuild/linux-mips64el@0.25.5': + optional: true + + '@esbuild/linux-ppc64@0.25.5': + optional: true + + '@esbuild/linux-riscv64@0.25.5': + optional: true + + '@esbuild/linux-s390x@0.25.5': + optional: true + + '@esbuild/linux-x64@0.25.5': + optional: true + + '@esbuild/netbsd-arm64@0.25.5': + optional: true + + '@esbuild/netbsd-x64@0.25.5': + optional: true + + '@esbuild/openbsd-arm64@0.25.5': + optional: true + + '@esbuild/openbsd-x64@0.25.5': + optional: true + + '@esbuild/sunos-x64@0.25.5': + optional: true + + '@esbuild/win32-arm64@0.25.5': + optional: true + + '@esbuild/win32-ia32@0.25.5': + optional: true + + '@esbuild/win32-x64@0.25.5': + optional: true + + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@napi-rs/wasm-runtime@0.2.11': + dependencies: + '@emnapi/core': 1.4.3 + '@emnapi/runtime': 1.4.3 + '@tybys/wasm-util': 0.9.0 + optional: true + + '@oxc-project/types@0.70.0': {} + + '@quansync/fs@0.1.3': + dependencies: + quansync: 0.2.10 + + '@rolldown/binding-darwin-arm64@1.0.0-beta.9': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-beta.9': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-beta.9': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.9': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.9': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-beta.9': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-beta.9': + dependencies: + '@napi-rs/wasm-runtime': 0.2.11 + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9': + optional: true + + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.9': + optional: true + + '@rolldown/pluginutils@1.0.0-beta.9': {} + + '@rollup/rollup-android-arm-eabi@4.44.0': + optional: true + + '@rollup/rollup-android-arm64@4.44.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.44.0': + optional: true + + '@rollup/rollup-darwin-x64@4.44.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.44.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.44.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.44.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.44.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.44.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.44.0': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.44.0': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.44.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.44.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.44.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.44.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.44.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.44.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.44.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.44.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.44.0': + optional: true + + '@tybys/wasm-util@0.9.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/chai@5.2.2': + dependencies: + '@types/deep-eql': 4.0.2 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.8': {} + + '@types/node@22.15.33': + dependencies: + undici-types: 6.21.0 + + '@types/uuid@10.0.0': {} + + '@types/ws@8.18.1': + dependencies: + '@types/node': 22.15.33 + + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.2 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.2.0 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.4(vite@7.0.0(@types/node@22.15.33)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0))': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 7.0.0(@types/node@22.15.33)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.4': + dependencies: + '@vitest/utils': 3.2.4 + pathe: 2.0.3 + strip-literal: 3.0.0 + + '@vitest/snapshot@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + magic-string: 0.30.17 + pathe: 2.0.3 + + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.3 + + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.1.4 + tinyrainbow: 2.0.0 + + ansis@4.1.0: {} + + args-tokenizer@0.3.0: {} + + assertion-error@2.0.1: {} + + ast-kit@2.1.0: + dependencies: + '@babel/parser': 7.27.5 + pathe: 2.0.3 + + atomic-sleep@1.0.0: {} + + birpc@2.4.0: {} + + bumpp@10.2.0: + dependencies: + ansis: 4.1.0 + args-tokenizer: 0.3.0 + c12: 3.0.4 + cac: 6.7.14 + escalade: 3.2.0 + jsonc-parser: 3.3.1 + package-manager-detector: 1.3.0 + semver: 7.7.2 + tinyexec: 1.0.1 + tinyglobby: 0.2.14 + yaml: 2.8.0 + transitivePeerDependencies: + - magicast + + c12@3.0.4: + dependencies: + chokidar: 4.0.3 + confbox: 0.2.2 + defu: 6.1.4 + dotenv: 16.5.0 + exsolve: 1.0.7 + giget: 2.0.0 + jiti: 2.4.2 + ohash: 2.0.11 + pathe: 2.0.3 + perfect-debounce: 1.0.0 + pkg-types: 2.1.0 + rc9: 2.1.2 + + cac@6.7.14: {} + + chai@5.2.0: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.4 + pathval: 2.0.1 + + check-error@2.1.1: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + citty@0.1.6: + dependencies: + consola: 3.4.2 + + confbox@0.2.2: {} + + consola@3.4.2: {} + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + deep-eql@5.0.2: {} + + defu@6.1.4: {} + + destr@2.0.5: {} + + diff@8.0.2: {} + + dotenv@16.5.0: {} + + dts-resolver@2.1.1: {} + + empathic@1.1.0: {} + + es-module-lexer@1.7.0: {} + + esbuild@0.25.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.5 + '@esbuild/android-arm': 0.25.5 + '@esbuild/android-arm64': 0.25.5 + '@esbuild/android-x64': 0.25.5 + '@esbuild/darwin-arm64': 0.25.5 + '@esbuild/darwin-x64': 0.25.5 + '@esbuild/freebsd-arm64': 0.25.5 + '@esbuild/freebsd-x64': 0.25.5 + '@esbuild/linux-arm': 0.25.5 + '@esbuild/linux-arm64': 0.25.5 + '@esbuild/linux-ia32': 0.25.5 + '@esbuild/linux-loong64': 0.25.5 + '@esbuild/linux-mips64el': 0.25.5 + '@esbuild/linux-ppc64': 0.25.5 + '@esbuild/linux-riscv64': 0.25.5 + '@esbuild/linux-s390x': 0.25.5 + '@esbuild/linux-x64': 0.25.5 + '@esbuild/netbsd-arm64': 0.25.5 + '@esbuild/netbsd-x64': 0.25.5 + '@esbuild/openbsd-arm64': 0.25.5 + '@esbuild/openbsd-x64': 0.25.5 + '@esbuild/sunos-x64': 0.25.5 + '@esbuild/win32-arm64': 0.25.5 + '@esbuild/win32-ia32': 0.25.5 + '@esbuild/win32-x64': 0.25.5 + + escalade@3.2.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + expect-type@1.2.1: {} + + exsolve@1.0.7: {} + + fast-redact@3.5.0: {} + + fdir@6.4.6(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + + fsevents@2.3.3: + optional: true + + get-tsconfig@4.10.1: + dependencies: + resolve-pkg-maps: 1.0.0 + + giget@2.0.0: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + defu: 6.1.4 + node-fetch-native: 1.6.6 + nypm: 0.6.0 + pathe: 2.0.3 + + happy-dom@17.6.3: + dependencies: + webidl-conversions: 7.0.0 + whatwg-mimetype: 3.0.0 + + hookable@5.5.3: {} + + jiti@2.4.2: {} + + js-tokens@9.0.1: {} + + jsesc@3.1.0: {} + + jsonc-parser@3.3.1: {} + + loupe@3.1.4: {} + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + node-fetch-native@1.6.6: {} + + nypm@0.6.0: + dependencies: + citty: 0.1.6 + consola: 3.4.2 + pathe: 2.0.3 + pkg-types: 2.1.0 + tinyexec: 0.3.2 + + ohash@2.0.11: {} + + on-exit-leak-free@2.1.2: {} + + package-manager-detector@1.3.0: {} + + pathe@2.0.3: {} + + pathval@2.0.1: {} + + perfect-debounce@1.0.0: {} + + picocolors@1.1.1: {} + + picomatch@4.0.2: {} + + pino-abstract-transport@2.0.0: + dependencies: + split2: 4.2.0 + + pino-std-serializers@7.0.0: {} + + pino@9.7.0: + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.5.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pino-std-serializers: 7.0.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.0 + thread-stream: 3.1.0 + + pkg-types@2.1.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.7 + pathe: 2.0.3 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + posthog-node@5.1.1: {} + + process-warning@5.0.0: {} + + quansync@0.2.10: {} + + quick-format-unescaped@4.0.4: {} + + rc9@2.1.2: + dependencies: + defu: 6.1.4 + destr: 2.0.5 + + readdirp@4.1.2: {} + + real-require@0.2.0: {} + + resolve-pkg-maps@1.0.0: {} + + rolldown-plugin-dts@0.13.12(rolldown@1.0.0-beta.9)(typescript@5.8.3): + dependencies: + '@babel/generator': 7.27.5 + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 + ast-kit: 2.1.0 + birpc: 2.4.0 + debug: 4.4.1 + dts-resolver: 2.1.1 + get-tsconfig: 4.10.1 + rolldown: 1.0.0-beta.9 + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - oxc-resolver + - supports-color + + rolldown@1.0.0-beta.9: + dependencies: + '@oxc-project/types': 0.70.0 + '@rolldown/pluginutils': 1.0.0-beta.9 + ansis: 4.1.0 + optionalDependencies: + '@rolldown/binding-darwin-arm64': 1.0.0-beta.9 + '@rolldown/binding-darwin-x64': 1.0.0-beta.9 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.9 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.9 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.9 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.9 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.9 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.9 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.9 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.9 + '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.9 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.9 + + rollup@4.44.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.44.0 + '@rollup/rollup-android-arm64': 4.44.0 + '@rollup/rollup-darwin-arm64': 4.44.0 + '@rollup/rollup-darwin-x64': 4.44.0 + '@rollup/rollup-freebsd-arm64': 4.44.0 + '@rollup/rollup-freebsd-x64': 4.44.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.44.0 + '@rollup/rollup-linux-arm-musleabihf': 4.44.0 + '@rollup/rollup-linux-arm64-gnu': 4.44.0 + '@rollup/rollup-linux-arm64-musl': 4.44.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.44.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.44.0 + '@rollup/rollup-linux-riscv64-gnu': 4.44.0 + '@rollup/rollup-linux-riscv64-musl': 4.44.0 + '@rollup/rollup-linux-s390x-gnu': 4.44.0 + '@rollup/rollup-linux-x64-gnu': 4.44.0 + '@rollup/rollup-linux-x64-musl': 4.44.0 + '@rollup/rollup-win32-arm64-msvc': 4.44.0 + '@rollup/rollup-win32-ia32-msvc': 4.44.0 + '@rollup/rollup-win32-x64-msvc': 4.44.0 + fsevents: 2.3.3 + + safe-stable-stringify@2.5.0: {} + + semver@7.7.2: {} + + siginfo@2.0.0: {} + + sonic-boom@4.2.0: + dependencies: + atomic-sleep: 1.0.0 + + source-map-js@1.2.1: {} + + split2@4.2.0: {} + + stackback@0.0.2: {} + + std-env@3.9.0: {} + + strip-literal@3.0.0: + dependencies: + js-tokens: 9.0.1 + + thread-stream@3.1.0: + dependencies: + real-require: 0.2.0 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyexec@1.0.1: {} + + tinyglobby@0.2.14: + dependencies: + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 + + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.3: {} + + tsdown@0.11.13(typescript@5.8.3): + dependencies: + ansis: 4.1.0 + cac: 6.7.14 + chokidar: 4.0.3 + debug: 4.4.1 + diff: 8.0.2 + empathic: 1.1.0 + hookable: 5.5.3 + rolldown: 1.0.0-beta.9 + rolldown-plugin-dts: 0.13.12(rolldown@1.0.0-beta.9)(typescript@5.8.3) + semver: 7.7.2 + tinyexec: 1.0.1 + tinyglobby: 0.2.14 + unconfig: 7.3.2 + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - '@oxc-project/runtime' + - '@typescript/native-preview' + - oxc-resolver + - supports-color + - vue-tsc + + tslib@2.8.1: + optional: true + + tsx@4.20.3: + dependencies: + esbuild: 0.25.5 + get-tsconfig: 4.10.1 + optionalDependencies: + fsevents: 2.3.3 + + typescript@5.8.3: {} + + unconfig@7.3.2: + dependencies: + '@quansync/fs': 0.1.3 + defu: 6.1.4 + jiti: 2.4.2 + quansync: 0.2.10 + + undici-types@6.21.0: {} + + uuid@11.1.0: {} + + vite-node@3.2.4(@types/node@22.15.33)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0): + dependencies: + cac: 6.7.14 + debug: 4.4.1 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 7.0.0(@types/node@22.15.33)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite@7.0.0(@types/node@22.15.33)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0): + dependencies: + esbuild: 0.25.5 + fdir: 6.4.6(picomatch@4.0.2) + picomatch: 4.0.2 + postcss: 8.5.6 + rollup: 4.44.0 + tinyglobby: 0.2.14 + optionalDependencies: + '@types/node': 22.15.33 + fsevents: 2.3.3 + jiti: 2.4.2 + tsx: 4.20.3 + yaml: 2.8.0 + + vitest@3.2.4(@types/node@22.15.33)(happy-dom@17.6.3)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0): + dependencies: + '@types/chai': 5.2.2 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@7.0.0(@types/node@22.15.33)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.2.0 + debug: 4.4.1 + expect-type: 1.2.1 + magic-string: 0.30.17 + pathe: 2.0.3 + picomatch: 4.0.2 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.14 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 7.0.0(@types/node@22.15.33)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + vite-node: 3.2.4(@types/node@22.15.33)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.15.33 + happy-dom: 17.6.3 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + webidl-conversions@7.0.0: {} + + whatwg-mimetype@3.0.0: {} + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + ws@8.18.2: {} + + yaml@2.8.0: {} diff --git a/libs/typescript/pnpm-workspace.yaml b/libs/typescript/pnpm-workspace.yaml index 28d8bfd5..aa33c1bc 100644 --- a/libs/typescript/pnpm-workspace.yaml +++ b/libs/typescript/pnpm-workspace.yaml @@ -1,3 +1,3 @@ packages: - - "computer/*" - - "core/*" + - "computer" + - "core" From 8f49e0e2bf99a000969604e6f770ee066e42c64c Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 25 Jun 2025 14:46:21 -0700 Subject: [PATCH 105/141] Update logging rules, authentication process for computer, update pnpm --- libs/typescript/{computer => }/.nvmrc | 0 .../computer/src/computer/providers/base.ts | 2 +- .../computer/src/computer/providers/cloud.ts | 18 +-- .../typescript/computer/src/interface/base.ts | 109 +++++++++++------- .../core/src/telemetry/clients/posthog.ts | 27 +++-- libs/typescript/package.json | 2 +- libs/typescript/pnpm-lock.yaml | 54 +++++++++ 7 files changed, 146 insertions(+), 66 deletions(-) rename libs/typescript/{computer => }/.nvmrc (100%) diff --git a/libs/typescript/computer/.nvmrc b/libs/typescript/.nvmrc similarity index 100% rename from libs/typescript/computer/.nvmrc rename to libs/typescript/.nvmrc diff --git a/libs/typescript/computer/src/computer/providers/base.ts b/libs/typescript/computer/src/computer/providers/base.ts index 6b94021d..16a2213e 100644 --- a/libs/typescript/computer/src/computer/providers/base.ts +++ b/libs/typescript/computer/src/computer/providers/base.ts @@ -4,7 +4,7 @@ import pino from 'pino'; import type { OSType } from '../../types'; import type { BaseComputerConfig, Display, VMProviderType } from '../types'; -const logger = pino({ name: 'computer-base' }); +const logger = pino({ name: 'computer.provider_base' }); /** * Base Computer class with shared functionality diff --git a/libs/typescript/computer/src/computer/providers/cloud.ts b/libs/typescript/computer/src/computer/providers/cloud.ts index b3854161..5614fb64 100644 --- a/libs/typescript/computer/src/computer/providers/cloud.ts +++ b/libs/typescript/computer/src/computer/providers/cloud.ts @@ -6,8 +6,6 @@ import { import type { CloudComputerConfig, VMProviderType } from '../types'; import { BaseComputer } from './base'; -const logger = pino({ name: 'computer-cloud' }); - /** * Cloud-specific computer implementation */ @@ -17,6 +15,8 @@ export class CloudComputer extends BaseComputer { private iface?: BaseComputerInterface; private initialized = false; + protected logger = pino({ name: 'computer.provider_cloud' }); + constructor(config: CloudComputerConfig) { super(config); this.apiKey = config.apiKey; @@ -31,14 +31,14 @@ export class CloudComputer extends BaseComputer { */ async run(): Promise { if (this.initialized) { - logger.info('Computer already initialized, skipping initialization'); + this.logger.info('Computer already initialized, skipping initialization'); return; } try { // For cloud provider, the VM is already running, we just need to connect const ipAddress = this.ip; - logger.info(`Connecting to cloud VM at ${ipAddress}`); + this.logger.info(`Connecting to cloud VM at ${ipAddress}`); // Create the interface with API key authentication this.iface = InterfaceFactory.createInterfaceForOS( @@ -49,13 +49,13 @@ export class CloudComputer extends BaseComputer { ); // Wait for the interface to be ready - logger.info('Waiting for interface to be ready...'); + this.logger.info('Waiting for interface to be ready...'); await this.iface.waitForReady(); this.initialized = true; - logger.info('Cloud computer ready'); + this.logger.info('Cloud computer ready'); } catch (error) { - logger.error(`Failed to initialize cloud computer: ${error}`); + this.logger.error(`Failed to initialize cloud computer: ${error}`); throw new Error(`Failed to initialize cloud computer: ${error}`); } } @@ -64,7 +64,7 @@ export class CloudComputer extends BaseComputer { * Stop the cloud computer (disconnect interface) */ async stop(): Promise { - logger.info('Disconnecting from cloud computer...'); + this.logger.info('Disconnecting from cloud computer...'); if (this.iface) { this.iface.disconnect(); @@ -72,7 +72,7 @@ export class CloudComputer extends BaseComputer { } this.initialized = false; - logger.info('Disconnected from cloud computer'); + this.logger.info('Disconnected from cloud computer'); } /** diff --git a/libs/typescript/computer/src/interface/base.ts b/libs/typescript/computer/src/interface/base.ts index 061ce599..21b63389 100644 --- a/libs/typescript/computer/src/interface/base.ts +++ b/libs/typescript/computer/src/interface/base.ts @@ -40,7 +40,7 @@ export abstract class BaseComputerInterface { protected apiKey?: string; protected vmName?: string; - protected logger = pino({ name: 'interface-base' }); + protected logger = pino({ name: 'computer.interface-base' }); constructor( ipAddress: string, @@ -108,48 +108,60 @@ export abstract class BaseComputerInterface { throw new Error(`Interface not ready after ${timeout} seconds`); } + /** + * Authenticate with the WebSocket server. + * This should be called immediately after the WebSocket connection is established. + */ + private async authenticate(): Promise { + if (!this.apiKey || !this.vmName) { + // No authentication needed + return; + } + + this.logger.info('Performing authentication handshake...'); + const authMessage = { + command: 'authenticate', + params: { + api_key: this.apiKey, + container_name: this.vmName, + }, + }; + + return new Promise((resolve, reject) => { + const authHandler = (data: WebSocket.RawData) => { + try { + const authResult = JSON.parse(data.toString()); + if (!authResult.success) { + const errorMsg = authResult.error || 'Authentication failed'; + this.logger.error(`Authentication failed: ${errorMsg}`); + this.ws.close(); + reject(new Error(`Authentication failed: ${errorMsg}`)); + } else { + this.logger.info('Authentication successful'); + this.ws.off('message', authHandler); + resolve(); + } + } catch (error) { + this.ws.off('message', authHandler); + reject(error); + } + }; + + this.ws.on('message', authHandler); + this.ws.send(JSON.stringify(authMessage)); + }); + } + /** * Connect to the WebSocket server. */ public async connect(): Promise { + // If the WebSocket is already open, check if we need to authenticate if (this.ws.readyState === WebSocket.OPEN) { - // send authentication message if needed - if (this.apiKey && this.vmName) { - this.logger.info('Performing authentication handshake...'); - const authMessage = { - command: 'authenticate', - params: { - api_key: this.apiKey, - container_name: this.vmName, - }, - }; - - return new Promise((resolve, reject) => { - const authHandler = (data: WebSocket.RawData) => { - try { - const authResult = JSON.parse(data.toString()); - if (!authResult.success) { - const errorMsg = authResult.error || 'Authentication failed'; - this.logger.error(`Authentication failed: ${errorMsg}`); - this.ws.close(); - reject(new Error(`Authentication failed: ${errorMsg}`)); - } else { - this.logger.info('Authentication successful'); - this.ws.off('message', authHandler); - resolve(); - } - } catch (error) { - this.ws.off('message', authHandler); - reject(error); - } - }; - - this.ws.on('message', authHandler); - this.ws.send(JSON.stringify(authMessage)); - }); - } - - return; + this.logger.info( + 'Websocket is open, ensuring authentication is complete.' + ); + return this.authenticate(); } // If the WebSocket is closed or closing, reinitialize it @@ -157,18 +169,31 @@ export abstract class BaseComputerInterface { this.ws.readyState === WebSocket.CLOSED || this.ws.readyState === WebSocket.CLOSING ) { + this.logger.info('Websocket is closed. Reinitializing connection.'); const headers: { [key: string]: string } = {}; if (this.apiKey && this.vmName) { headers['X-API-Key'] = this.apiKey; headers['X-VM-Name'] = this.vmName; } this.ws = new WebSocket(this.wsUri, { headers }); + return this.authenticate(); } + // Connect and authenticate return new Promise((resolve, reject) => { - // If already connecting, wait for it to complete + const onOpen = async () => { + try { + // Always authenticate immediately after connection + await this.authenticate(); + resolve(); + } catch (error) { + reject(error); + } + }; + + // If already connecting, wait for it to complete then authenticate if (this.ws.readyState === WebSocket.CONNECTING) { - this.ws.addEventListener('open', () => resolve(), { once: true }); + this.ws.addEventListener('open', onOpen, { once: true }); this.ws.addEventListener('error', (error) => reject(error), { once: true, }); @@ -176,9 +201,7 @@ export abstract class BaseComputerInterface { } // Set up event handlers - this.ws.on('open', () => { - resolve(); - }); + this.ws.on('open', onOpen); this.ws.on('error', (error: Error) => { reject(error); diff --git a/libs/typescript/core/src/telemetry/clients/posthog.ts b/libs/typescript/core/src/telemetry/clients/posthog.ts index 0ef9df85..e42bc449 100644 --- a/libs/typescript/core/src/telemetry/clients/posthog.ts +++ b/libs/typescript/core/src/telemetry/clients/posthog.ts @@ -8,7 +8,6 @@ import * as path from 'node:path'; import { pino } from 'pino'; import { PostHog } from 'posthog-node'; import { v4 as uuidv4 } from 'uuid'; -const logger = pino({ name: 'core.telemetry' }); // Controls how frequently telemetry will be sent (percentage) export const TELEMETRY_SAMPLE_RATE = 100; // 100% sampling rate @@ -37,6 +36,8 @@ export class PostHogTelemetryClient { private posthogClient?: PostHog; private counters: Record = {}; + private logger = pino({ name: 'core.telemetry' }); + constructor() { // set up config this.config = { @@ -63,11 +64,13 @@ export class PostHogTelemetryClient { // Log telemetry status on startup if (this.config.enabled) { - logger.info(`Telemetry enabled (sampling at ${this.config.sampleRate}%)`); + this.logger.info( + `Telemetry enabled (sampling at ${this.config.sampleRate}%)` + ); // Initialize PostHog client if config is available this._initializePosthog(); } else { - logger.info('Telemetry disabled'); + this.logger.info('Telemetry disabled'); } } @@ -84,7 +87,7 @@ export class PostHogTelemetryClient { return fs.readFileSync(idFile, 'utf-8').trim(); } } catch (error) { - logger.debug(`Failed to read installation ID: ${error}`); + this.logger.debug(`Failed to read installation ID: ${error}`); } // Create new ID if not exists @@ -97,7 +100,7 @@ export class PostHogTelemetryClient { fs.writeFileSync(idFile, newId); return newId; } catch (error) { - logger.debug(`Failed to write installation ID: ${error}`); + this.logger.debug(`Failed to write installation ID: ${error}`); } // Fallback to in-memory ID if file operations fail @@ -119,13 +122,13 @@ export class PostHogTelemetryClient { flushInterval: 30000, // Send events every 30 seconds }); this.initialized = true; - logger.debug('PostHog client initialized successfully'); + this.logger.debug('PostHog client initialized successfully'); // Process any queued events this._processQueuedEvents(); return true; } catch (error) { - logger.error(`Failed to initialize PostHog client: ${error}`); + this.logger.error(`Failed to initialize PostHog client: ${error}`); return false; } } @@ -171,7 +174,7 @@ export class PostHogTelemetryClient { properties: eventProperties, }); } catch (error) { - logger.debug(`Failed to capture event: ${error}`); + this.logger.debug(`Failed to capture event: ${error}`); } } @@ -257,13 +260,13 @@ export class PostHogTelemetryClient { } await this.posthogClient.flush(); - logger.debug('Telemetry flushed successfully'); + this.logger.debug('Telemetry flushed successfully'); // Clear counters after sending this.counters = {}; return true; } catch (error) { - logger.debug(`Failed to flush telemetry: ${error}`); + this.logger.debug(`Failed to flush telemetry: ${error}`); return false; } } @@ -273,7 +276,7 @@ export class PostHogTelemetryClient { * Enable telemetry collection. */ this.config.enabled = true; - logger.info('Telemetry enabled'); + this.logger.info('Telemetry enabled'); if (!this.initialized) { this._initializePosthog(); } @@ -285,7 +288,7 @@ export class PostHogTelemetryClient { */ this.config.enabled = false; await this.posthogClient?.disable(); - logger.info('Telemetry disabled'); + this.logger.info('Telemetry disabled'); } get enabled(): boolean { diff --git a/libs/typescript/package.json b/libs/typescript/package.json index 1b054bc6..134f8600 100644 --- a/libs/typescript/package.json +++ b/libs/typescript/package.json @@ -16,7 +16,7 @@ "test": "pnpm -r test", "typecheck": "pnpm -r typecheck" }, - "packageManager": "pnpm@10.6.5", + "packageManager": "pnpm@10.12.3", "devDependencies": { "@biomejs/biome": "^1.9.4" }, diff --git a/libs/typescript/pnpm-lock.yaml b/libs/typescript/pnpm-lock.yaml index c2f97fe7..69ea7745 100644 --- a/libs/typescript/pnpm-lock.yaml +++ b/libs/typescript/pnpm-lock.yaml @@ -95,6 +95,44 @@ importers: specifier: ^3.1.3 version: 3.2.4(@types/node@22.15.33)(happy-dom@17.6.3)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + examples/byo-operator-ts: + dependencies: + '@cua/computer': + specifier: link:../../computer + version: link:../../computer + openai: + specifier: ^5.7.0 + version: 5.7.0(ws@8.18.2) + devDependencies: + tsx: + specifier: ^4.20.3 + version: 4.20.3 + typescript: + specifier: ^5.8.3 + version: 5.8.3 + + examples/cua-openai: + dependencies: + '@cua/computer': + specifier: link:../../computer + version: link:../../computer + dotenv: + specifier: ^16.5.0 + version: 16.5.0 + openai: + specifier: ^5.7.0 + version: 5.7.0(ws@8.18.2) + devDependencies: + '@types/node': + specifier: ^22.15.33 + version: 22.15.33 + tsx: + specifier: ^4.20.3 + version: 4.20.3 + typescript: + specifier: ^5.8.3 + version: 5.8.3 + packages: '@babel/generator@7.27.5': @@ -769,6 +807,18 @@ packages: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} + openai@5.7.0: + resolution: {integrity: sha512-zXWawZl6J/P5Wz57/nKzVT3kJQZvogfuyuNVCdEp4/XU2UNrjL7SsuNpWAyLZbo6HVymwmnfno9toVzBhelygA==} + hasBin: true + peerDependencies: + ws: ^8.18.0 + zod: ^3.23.8 + peerDependenciesMeta: + ws: + optional: true + zod: + optional: true + package-manager-detector@1.3.0: resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} @@ -1601,6 +1651,10 @@ snapshots: on-exit-leak-free@2.1.2: {} + openai@5.7.0(ws@8.18.2): + optionalDependencies: + ws: 8.18.2 + package-manager-detector@1.3.0: {} pathe@2.0.3: {} From 3e4620431dc783474c89ee5e61ecad8a3a0dbc7b Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 25 Jun 2025 15:54:24 -0700 Subject: [PATCH 106/141] Create example for using @cua/computer with cloud container and openai --- .../examples/cua-cloud-openai/.env.example | 3 + .../examples/cua-cloud-openai/.gitignore | 3 + .../examples/cua-cloud-openai/.prettierrc | 7 ++ .../examples/cua-cloud-openai/package.json | 25 +++++ .../examples/cua-cloud-openai/src/helpers.ts | 56 ++++++++++ .../examples/cua-cloud-openai/src/index.ts | 104 ++++++++++++++++++ .../examples/cua-cloud-openai/tsconfig.json | 29 +++++ 7 files changed, 227 insertions(+) create mode 100644 libs/typescript/examples/cua-cloud-openai/.env.example create mode 100644 libs/typescript/examples/cua-cloud-openai/.gitignore create mode 100644 libs/typescript/examples/cua-cloud-openai/.prettierrc create mode 100644 libs/typescript/examples/cua-cloud-openai/package.json create mode 100644 libs/typescript/examples/cua-cloud-openai/src/helpers.ts create mode 100644 libs/typescript/examples/cua-cloud-openai/src/index.ts create mode 100644 libs/typescript/examples/cua-cloud-openai/tsconfig.json diff --git a/libs/typescript/examples/cua-cloud-openai/.env.example b/libs/typescript/examples/cua-cloud-openai/.env.example new file mode 100644 index 00000000..0496a574 --- /dev/null +++ b/libs/typescript/examples/cua-cloud-openai/.env.example @@ -0,0 +1,3 @@ +OPENAI_KEY= +CUA_KEY= +CUA_CONTAINER_NAME= \ No newline at end of file diff --git a/libs/typescript/examples/cua-cloud-openai/.gitignore b/libs/typescript/examples/cua-cloud-openai/.gitignore new file mode 100644 index 00000000..9bdf3559 --- /dev/null +++ b/libs/typescript/examples/cua-cloud-openai/.gitignore @@ -0,0 +1,3 @@ +node_modules +.DS_Store +.env \ No newline at end of file diff --git a/libs/typescript/examples/cua-cloud-openai/.prettierrc b/libs/typescript/examples/cua-cloud-openai/.prettierrc new file mode 100644 index 00000000..23eaef29 --- /dev/null +++ b/libs/typescript/examples/cua-cloud-openai/.prettierrc @@ -0,0 +1,7 @@ +{ + "useTabs": false, + "semi": true, + "singleQuote": true, + "trailingComma": "es5", + "bracketSpacing": true +} \ No newline at end of file diff --git a/libs/typescript/examples/cua-cloud-openai/package.json b/libs/typescript/examples/cua-cloud-openai/package.json new file mode 100644 index 00000000..3d769cb0 --- /dev/null +++ b/libs/typescript/examples/cua-cloud-openai/package.json @@ -0,0 +1,25 @@ +{ + "name": "cua-cloud-openai", + "version": "1.0.0", + "description": "", + "type": "module", + "main": "index.js", + "scripts": { + "dev": "tsx watch src/index.ts", + "start": "tsx src/index.ts" + }, + "keywords": [], + "author": "", + "license": "MIT", + "packageManager": "pnpm@10.12.3", + "dependencies": { + "@cua/computer": "link:../../computer", + "dotenv": "^16.5.0", + "openai": "^5.7.0" + }, + "devDependencies": { + "@types/node": "^22.15.33", + "tsx": "^4.20.3", + "typescript": "^5.8.3" + } +} \ No newline at end of file diff --git a/libs/typescript/examples/cua-cloud-openai/src/helpers.ts b/libs/typescript/examples/cua-cloud-openai/src/helpers.ts new file mode 100644 index 00000000..68062cd4 --- /dev/null +++ b/libs/typescript/examples/cua-cloud-openai/src/helpers.ts @@ -0,0 +1,56 @@ +import { Computer } from "@cua/computer"; +import OpenAI from "openai"; + +export async function executeAction( + computer: Computer, + action: OpenAI.Responses.ResponseComputerToolCall['action'] +) { + switch (action.type) { + case 'click': + const { x, y, button } = action; + console.log(`Executing click at (${x}, ${y}) with button '${button}'.`); + await computer.interface.moveCursor(x, y); + if (button === 'right') await computer.interface.rightClick(); + else await computer.interface.leftClick(); + break; + case 'type': + const { text } = action; + console.log(`Typing text: ${text}`); + await computer.interface.typeText(text); + break; + case 'scroll': + const { x: locX, y: locY, scroll_x, scroll_y } = action; + console.log( + `Scrolling at (${locX}, ${locY}) with offsets (scroll_x=${scroll_x}, scroll_y=${scroll_y}).` + ); + await computer.interface.moveCursor(locX, locY); + await computer.interface.scroll(scroll_x, scroll_y); + break; + case 'keypress': + const { keys } = action; + for (const key of keys) { + console.log(`Pressing key: ${key}.`); + // Map common key names to CUA equivalents + if (key.toLowerCase() === 'enter') { + await computer.interface.pressKey('return'); + } else if (key.toLowerCase() === 'space') { + await computer.interface.pressKey('space'); + } else { + await computer.interface.pressKey(key); + } + } + break; + case 'wait': + console.log(`Waiting for 3 seconds.`); + await new Promise((resolve) => setTimeout(resolve, 3 * 1000)); + break; + case 'screenshot': + console.log('Taking screenshot.'); + // This is handled automatically in the main loop, but we can take an extra one if requested + const screenshot = await computer.interface.screenshot(); + return screenshot; + default: + console.log(`Unrecognized action: ${action.type}`); + break; + } +} diff --git a/libs/typescript/examples/cua-cloud-openai/src/index.ts b/libs/typescript/examples/cua-cloud-openai/src/index.ts new file mode 100644 index 00000000..eb8b0022 --- /dev/null +++ b/libs/typescript/examples/cua-cloud-openai/src/index.ts @@ -0,0 +1,104 @@ +import { Computer, OSType } from '@cua/computer'; +import OpenAI from 'openai'; +import { executeAction } from './helpers'; + +import 'dotenv/config'; + +const openai = new OpenAI({ apiKey: process.env.OPENAI_KEY }); + +const COMPUTER_USE_PROMPT = 'Open firefox and go to trycua.com'; + +// Initialize the Computer Connection +const computer = new Computer({ + apiKey: process.env.CUA_KEY!, + name: process.env.CUA_CONTAINER_NAME!, + osType: OSType.LINUX, +}); + +await computer.run(); +// Take the initial screenshot +const screenshot = await computer.interface.screenshot(); +const screenshotBase64 = screenshot.toString('base64'); + +// Setup openai config for computer use +const computerUseConfig: OpenAI.Responses.ResponseCreateParamsNonStreaming = { + model: 'computer-use-preview', + tools: [ + { + type: 'computer_use_preview', + display_width: 1024, + display_height: 768, + environment: 'linux', // we're using a linux vm + }, + ], + truncation: 'auto', +}; + +// Send initial screenshot to the openai computer use model +let res = await openai.responses.create({ + ...computerUseConfig, + input: [ + { + role: 'user', + content: [ + // what we want the ai to do + { type: 'input_text', text: COMPUTER_USE_PROMPT }, + // current screenshot of the vm + { + type: 'input_image', + image_url: `data:image/png;base64,${screenshotBase64}`, + detail: 'auto', + }, + ], + }, + ], +}); + +// Loop until there are no more computer use actions. +while (true) { + const computerCalls = res.output.filter((o) => o.type === 'computer_call'); + if (computerCalls.length < 1) { + console.log('No more computer calls. Loop complete.'); + break; + } + // Get the first call + const call = computerCalls[0]; + const action = call.action; + console.log('Received action from OpenAI Responses API:', action); + let ackChecks: OpenAI.Responses.ResponseComputerToolCall.PendingSafetyCheck[] = + []; + if (call.pending_safety_checks.length > 0) { + console.log('Safety checks pending:', call.pending_safety_checks); + // In a real implementation, you would want to get user confirmation here + ackChecks = call.pending_safety_checks; + } + + // Execute the action in the container + await executeAction(computer, action); + // Wait for changes to process within the container (1sec) + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // Capture new screenshot + const newScreenshot = await computer.interface.screenshot(); + const newScreenshotBase64 = newScreenshot.toString('base64'); + + // Screenshot back as computer_call_output + + res = await openai.responses.create({ + ...computerUseConfig, + previous_response_id: res.id, + input: [ + { + type: 'computer_call_output', + call_id: call.call_id, + acknowledged_safety_checks: ackChecks, + output: { + type: 'computer_screenshot', + image_url: `data:image/png;base64,${newScreenshotBase64}`, + }, + }, + ], + }); +} + +process.exit(); diff --git a/libs/typescript/examples/cua-cloud-openai/tsconfig.json b/libs/typescript/examples/cua-cloud-openai/tsconfig.json new file mode 100644 index 00000000..c606e279 --- /dev/null +++ b/libs/typescript/examples/cua-cloud-openai/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": [ + "es2023" + ], + "moduleDetection": "force", + "module": "preserve", + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "types": [ + "node" + ], + "allowSyntheticDefaultImports": true, + "strict": true, + "noUnusedLocals": true, + "declaration": true, + "emitDeclarationOnly": true, + "esModuleInterop": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true, + "outDir": "build", + }, + "include": [ + "src" + ] +} \ No newline at end of file From d2874e078f43731fcabd97adb4016e774db3470a Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 25 Jun 2025 15:56:52 -0700 Subject: [PATCH 107/141] Create readme --- .../examples/cua-cloud-openai/README.md | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 libs/typescript/examples/cua-cloud-openai/README.md diff --git a/libs/typescript/examples/cua-cloud-openai/README.md b/libs/typescript/examples/cua-cloud-openai/README.md new file mode 100644 index 00000000..3f4fe8c1 --- /dev/null +++ b/libs/typescript/examples/cua-cloud-openai/README.md @@ -0,0 +1,45 @@ +# cua-cloud-openai Example + +This example demonstrates how to control a c/ua Cloud container using the OpenAI `computer-use-preview` model and the `@cua/computer` TypeScript library. + +## Overview + +- Connects to a c/ua Cloud container via the `@cua/computer` library +- Sends screenshots and instructions to OpenAI's computer-use model +- Executes AI-generated actions (clicks, typing, etc.) inside the container +- Designed for Linux containers, but can be adapted for other OS types + +## Getting Started + +1. **Install dependencies:** + ```bash + npm install + ``` + +2. **Set up environment variables:** + Create a `.env` file with the following variables: + - `OPENAI_KEY` — your OpenAI API key + - `CUA_KEY` — your c/ua Cloud API key + - `CUA_CONTAINER_NAME` — the name of your provisioned container + +3. **Run the example:** + ```bash + npx tsx src/index.ts + ``` + +## Files + +- `src/index.ts` — Main example script +- `src/helpers.ts` — Helper for executing actions on the container + +## Further Reading + +For a step-by-step tutorial and more detailed explanation, see the accompanying blog post: + +➡️ [Controlling a c/ua Cloud Container with JavaScript](https://placeholder-url-to-blog-post.com) + +_(This link will be updated once the article is published.)_ + +--- + +If you have questions or issues, please open an issue or contact the maintainers. From 83499550e3b42c4e53e601d944614a52420c04dd Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 25 Jun 2025 16:10:08 -0700 Subject: [PATCH 108/141] Move non-python libs to respective directories. Update workflows to use updated directories --- .github/workflows/ci-lume.yml | 4 +- .github/workflows/publish-agent.yml | 218 ++++---- .github/workflows/publish-computer-server.yml | 66 +-- .github/workflows/publish-computer.yml | 182 +++---- .github/workflows/publish-core.yml | 52 +- .github/workflows/publish-lume.yml | 82 +-- .github/workflows/publish-mcp-server.yml | 204 ++++---- .github/workflows/publish-pylume.yml | 72 +-- .github/workflows/publish-som.yml | 52 +- .github/workflows/reusable-publish.yml | 476 +++++++++--------- libs/{python => bash}/lumier/.dockerignore | 0 libs/{python => bash}/lumier/Dockerfile | 0 libs/{python => bash}/lumier/README.md | 0 libs/{python => bash}/lumier/src/bin/entry.sh | 0 .../lumier/src/config/constants.sh | 0 .../lumier/src/hooks/on-logon.sh | 0 libs/{python => bash}/lumier/src/lib/utils.sh | 0 libs/{python => bash}/lumier/src/lib/vm.sh | 0 libs/{python => swift}/lume/.cursorignore | 0 libs/{python => swift}/lume/CONTRIBUTING.md | 0 libs/{python => swift}/lume/Package.resolved | 0 libs/{python => swift}/lume/Package.swift | 0 libs/{python => swift}/lume/README.md | 0 .../lume/docs/API-Reference.md | 0 .../lume/docs/Development.md | 0 libs/{python => swift}/lume/docs/FAQ.md | 0 libs/{python => swift}/lume/img/cli.png | Bin .../{python => swift}/lume/img/logo_black.png | Bin .../{python => swift}/lume/img/logo_white.png | Bin .../lume/resources/lume.entitlements | 0 .../{python => swift}/lume/scripts/install.sh | 0 .../lume/src/Commands/Clone.swift | 0 .../lume/src/Commands/Config.swift | 0 .../lume/src/Commands/Create.swift | 0 .../lume/src/Commands/Delete.swift | 0 .../lume/src/Commands/Get.swift | 0 .../lume/src/Commands/IPSW.swift | 0 .../lume/src/Commands/Images.swift | 0 .../lume/src/Commands/List.swift | 0 .../lume/src/Commands/Logs.swift | 0 .../src/Commands/Options/FormatOption.swift | 0 .../lume/src/Commands/Prune.swift | 0 .../lume/src/Commands/Pull.swift | 0 .../lume/src/Commands/Push.swift | 0 .../lume/src/Commands/Run.swift | 0 .../lume/src/Commands/Serve.swift | 0 .../lume/src/Commands/Set.swift | 0 .../lume/src/Commands/Stop.swift | 0 .../ImageContainerRegistry.swift | 0 .../src/ContainerRegistry/ImageList.swift | 0 .../src/ContainerRegistry/ImagesPrinter.swift | 0 .../lume/src/Errors/Errors.swift | 0 .../lume/src/FileSystem/Home.swift | 0 .../lume/src/FileSystem/Settings.swift | 0 .../lume/src/FileSystem/VMConfig.swift | 0 .../lume/src/FileSystem/VMDirectory.swift | 0 .../lume/src/FileSystem/VMLocation.swift | 0 .../lume/src/LumeController.swift | 0 libs/{python => swift}/lume/src/Main.swift | 0 .../lume/src/Server/HTTP.swift | 0 .../lume/src/Server/Handlers.swift | 0 .../lume/src/Server/Requests.swift | 0 .../lume/src/Server/Responses.swift | 0 .../lume/src/Server/Server.swift | 0 .../lume/src/Utils/CommandRegistry.swift | 0 .../lume/src/Utils/CommandUtils.swift | 0 .../lume/src/Utils/Logger.swift | 0 .../lume/src/Utils/NetworkUtils.swift | 0 .../lume/src/Utils/Path.swift | 0 .../lume/src/Utils/ProcessRunner.swift | 0 .../lume/src/Utils/ProgressLogger.swift | 0 .../lume/src/Utils/String.swift | 0 .../lume/src/Utils/Utils.swift | 0 .../lume/src/VM/DarwinVM.swift | 0 .../lume/src/VM/LinuxVM.swift | 0 libs/{python => swift}/lume/src/VM/VM.swift | 0 .../lume/src/VM/VMDetails.swift | 0 .../lume/src/VM/VMDetailsPrinter.swift | 0 .../lume/src/VM/VMDisplayResolution.swift | 0 .../lume/src/VM/VMFactory.swift | 0 .../lume/src/VNC/PassphraseGenerator.swift | 0 .../lume/src/VNC/VNCService.swift | 0 .../src/Virtualization/DHCPLeaseParser.swift | 0 .../Virtualization/DarwinImageLoader.swift | 0 .../Virtualization/ImageLoaderFactory.swift | 0 .../VMVirtualizationService.swift | 0 .../lume/tests/Mocks/MockVM.swift | 0 .../Mocks/MockVMVirtualizationService.swift | 0 .../lume/tests/Mocks/MockVNCService.swift | 0 .../lume/tests/VM/VMDetailsPrinterTests.swift | 0 .../lume/tests/VMTests.swift | 0 .../tests/VMVirtualizationServiceTests.swift | 0 .../lume/tests/VNCServiceTests.swift | 0 93 files changed, 704 insertions(+), 704 deletions(-) rename libs/{python => bash}/lumier/.dockerignore (100%) rename libs/{python => bash}/lumier/Dockerfile (100%) rename libs/{python => bash}/lumier/README.md (100%) rename libs/{python => bash}/lumier/src/bin/entry.sh (100%) rename libs/{python => bash}/lumier/src/config/constants.sh (100%) rename libs/{python => bash}/lumier/src/hooks/on-logon.sh (100%) rename libs/{python => bash}/lumier/src/lib/utils.sh (100%) rename libs/{python => bash}/lumier/src/lib/vm.sh (100%) rename libs/{python => swift}/lume/.cursorignore (100%) rename libs/{python => swift}/lume/CONTRIBUTING.md (100%) rename libs/{python => swift}/lume/Package.resolved (100%) rename libs/{python => swift}/lume/Package.swift (100%) rename libs/{python => swift}/lume/README.md (100%) rename libs/{python => swift}/lume/docs/API-Reference.md (100%) rename libs/{python => swift}/lume/docs/Development.md (100%) rename libs/{python => swift}/lume/docs/FAQ.md (100%) rename libs/{python => swift}/lume/img/cli.png (100%) rename libs/{python => swift}/lume/img/logo_black.png (100%) rename libs/{python => swift}/lume/img/logo_white.png (100%) rename libs/{python => swift}/lume/resources/lume.entitlements (100%) rename libs/{python => swift}/lume/scripts/install.sh (100%) rename libs/{python => swift}/lume/src/Commands/Clone.swift (100%) rename libs/{python => swift}/lume/src/Commands/Config.swift (100%) rename libs/{python => swift}/lume/src/Commands/Create.swift (100%) rename libs/{python => swift}/lume/src/Commands/Delete.swift (100%) rename libs/{python => swift}/lume/src/Commands/Get.swift (100%) rename libs/{python => swift}/lume/src/Commands/IPSW.swift (100%) rename libs/{python => swift}/lume/src/Commands/Images.swift (100%) rename libs/{python => swift}/lume/src/Commands/List.swift (100%) rename libs/{python => swift}/lume/src/Commands/Logs.swift (100%) rename libs/{python => swift}/lume/src/Commands/Options/FormatOption.swift (100%) rename libs/{python => swift}/lume/src/Commands/Prune.swift (100%) rename libs/{python => swift}/lume/src/Commands/Pull.swift (100%) rename libs/{python => swift}/lume/src/Commands/Push.swift (100%) rename libs/{python => swift}/lume/src/Commands/Run.swift (100%) rename libs/{python => swift}/lume/src/Commands/Serve.swift (100%) rename libs/{python => swift}/lume/src/Commands/Set.swift (100%) rename libs/{python => swift}/lume/src/Commands/Stop.swift (100%) rename libs/{python => swift}/lume/src/ContainerRegistry/ImageContainerRegistry.swift (100%) rename libs/{python => swift}/lume/src/ContainerRegistry/ImageList.swift (100%) rename libs/{python => swift}/lume/src/ContainerRegistry/ImagesPrinter.swift (100%) rename libs/{python => swift}/lume/src/Errors/Errors.swift (100%) rename libs/{python => swift}/lume/src/FileSystem/Home.swift (100%) rename libs/{python => swift}/lume/src/FileSystem/Settings.swift (100%) rename libs/{python => swift}/lume/src/FileSystem/VMConfig.swift (100%) rename libs/{python => swift}/lume/src/FileSystem/VMDirectory.swift (100%) rename libs/{python => swift}/lume/src/FileSystem/VMLocation.swift (100%) rename libs/{python => swift}/lume/src/LumeController.swift (100%) rename libs/{python => swift}/lume/src/Main.swift (100%) rename libs/{python => swift}/lume/src/Server/HTTP.swift (100%) rename libs/{python => swift}/lume/src/Server/Handlers.swift (100%) rename libs/{python => swift}/lume/src/Server/Requests.swift (100%) rename libs/{python => swift}/lume/src/Server/Responses.swift (100%) rename libs/{python => swift}/lume/src/Server/Server.swift (100%) rename libs/{python => swift}/lume/src/Utils/CommandRegistry.swift (100%) rename libs/{python => swift}/lume/src/Utils/CommandUtils.swift (100%) rename libs/{python => swift}/lume/src/Utils/Logger.swift (100%) rename libs/{python => swift}/lume/src/Utils/NetworkUtils.swift (100%) rename libs/{python => swift}/lume/src/Utils/Path.swift (100%) rename libs/{python => swift}/lume/src/Utils/ProcessRunner.swift (100%) rename libs/{python => swift}/lume/src/Utils/ProgressLogger.swift (100%) rename libs/{python => swift}/lume/src/Utils/String.swift (100%) rename libs/{python => swift}/lume/src/Utils/Utils.swift (100%) rename libs/{python => swift}/lume/src/VM/DarwinVM.swift (100%) rename libs/{python => swift}/lume/src/VM/LinuxVM.swift (100%) rename libs/{python => swift}/lume/src/VM/VM.swift (100%) rename libs/{python => swift}/lume/src/VM/VMDetails.swift (100%) rename libs/{python => swift}/lume/src/VM/VMDetailsPrinter.swift (100%) rename libs/{python => swift}/lume/src/VM/VMDisplayResolution.swift (100%) rename libs/{python => swift}/lume/src/VM/VMFactory.swift (100%) rename libs/{python => swift}/lume/src/VNC/PassphraseGenerator.swift (100%) rename libs/{python => swift}/lume/src/VNC/VNCService.swift (100%) rename libs/{python => swift}/lume/src/Virtualization/DHCPLeaseParser.swift (100%) rename libs/{python => swift}/lume/src/Virtualization/DarwinImageLoader.swift (100%) rename libs/{python => swift}/lume/src/Virtualization/ImageLoaderFactory.swift (100%) rename libs/{python => swift}/lume/src/Virtualization/VMVirtualizationService.swift (100%) rename libs/{python => swift}/lume/tests/Mocks/MockVM.swift (100%) rename libs/{python => swift}/lume/tests/Mocks/MockVMVirtualizationService.swift (100%) rename libs/{python => swift}/lume/tests/Mocks/MockVNCService.swift (100%) rename libs/{python => swift}/lume/tests/VM/VMDetailsPrinterTests.swift (100%) rename libs/{python => swift}/lume/tests/VMTests.swift (100%) rename libs/{python => swift}/lume/tests/VMVirtualizationServiceTests.swift (100%) rename libs/{python => swift}/lume/tests/VNCServiceTests.swift (100%) diff --git a/.github/workflows/ci-lume.yml b/.github/workflows/ci-lume.yml index d33191cc..dc31bd5a 100644 --- a/.github/workflows/ci-lume.yml +++ b/.github/workflows/ci-lume.yml @@ -20,7 +20,7 @@ jobs: - run: uname -a - run: sudo xcode-select -s /Applications/Xcode_16.app # Swift 6.0 - run: swift test - working-directory: ./libs/lume + working-directory: ./libs/swift/lume build: name: Release build runs-on: macos-15 @@ -29,4 +29,4 @@ jobs: - run: uname -a - run: sudo xcode-select -s /Applications/Xcode_16.app # Swift 6.0 - run: swift build --configuration release - working-directory: ./libs/lume + working-directory: ./libs/swift/lume diff --git a/.github/workflows/publish-agent.yml b/.github/workflows/publish-agent.yml index ea03edd6..5869bcd1 100644 --- a/.github/workflows/publish-agent.yml +++ b/.github/workflows/publish-agent.yml @@ -3,17 +3,17 @@ name: Publish Agent Package on: push: tags: - - 'agent-v*' + - "agent-v*" workflow_dispatch: inputs: version: - description: 'Version to publish (without v prefix)' + description: "Version to publish (without v prefix)" required: true - default: '0.1.0' + default: "0.1.0" workflow_call: inputs: version: - description: 'Version to publish' + description: "Version to publish" required: true type: string @@ -30,127 +30,127 @@ jobs: som_version: ${{ steps.update-deps.outputs.som_version }} core_version: ${{ steps.update-deps.outputs.core_version }} steps: - - uses: actions/checkout@v4 - - - name: Determine version - id: get-version - run: | - if [ "${{ github.event_name }}" == "push" ]; then - # Extract version from tag (for package-specific tags) - if [[ "${{ github.ref }}" =~ ^refs/tags/agent-v([0-9]+\.[0-9]+\.[0-9]+) ]]; then - VERSION=${BASH_REMATCH[1]} + - uses: actions/checkout@v4 + + - name: Determine version + id: get-version + run: | + if [ "${{ github.event_name }}" == "push" ]; then + # Extract version from tag (for package-specific tags) + if [[ "${{ github.ref }}" =~ ^refs/tags/agent-v([0-9]+\.[0-9]+\.[0-9]+) ]]; then + VERSION=${BASH_REMATCH[1]} + else + echo "Invalid tag format for agent" + exit 1 + fi + elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + # Use version from workflow dispatch + VERSION=${{ github.event.inputs.version }} else - echo "Invalid tag format for agent" - exit 1 + # Use version from workflow_call + VERSION=${{ inputs.version }} fi - elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then - # Use version from workflow dispatch - VERSION=${{ github.event.inputs.version }} - else - # Use version from workflow_call - VERSION=${{ inputs.version }} - fi - echo "VERSION=$VERSION" - echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "VERSION=$VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.11' + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" - - name: Update dependencies to latest versions - id: update-deps - run: | - cd libs/agent - - # Install required package for PyPI API access - pip install requests - - # Create a more robust Python script for PyPI version checking - cat > get_latest_versions.py << 'EOF' - import requests - import json - import sys + - name: Update dependencies to latest versions + id: update-deps + run: | + cd libs/python/agent - def get_package_version(package_name, fallback="0.1.0"): - try: - response = requests.get(f'https://pypi.org/pypi/{package_name}/json') - print(f"API Response Status for {package_name}: {response.status_code}", file=sys.stderr) - - if response.status_code != 200: - print(f"API request failed for {package_name}, using fallback version", file=sys.stderr) - return fallback - - data = json.loads(response.text) - - if 'info' not in data: - print(f"Missing 'info' key in API response for {package_name}, using fallback version", file=sys.stderr) - return fallback - - return data['info']['version'] - except Exception as e: - print(f"Error fetching version for {package_name}: {str(e)}", file=sys.stderr) - return fallback + # Install required package for PyPI API access + pip install requests - # Get latest versions - print(get_package_version('cua-computer')) - print(get_package_version('cua-som')) - print(get_package_version('cua-core')) - EOF - - # Execute the script to get the versions - VERSIONS=($(python get_latest_versions.py)) - LATEST_COMPUTER=${VERSIONS[0]} - LATEST_SOM=${VERSIONS[1]} - LATEST_CORE=${VERSIONS[2]} - - echo "Latest cua-computer version: $LATEST_COMPUTER" - echo "Latest cua-som version: $LATEST_SOM" - echo "Latest cua-core version: $LATEST_CORE" - - # Output the versions for the next job - echo "computer_version=$LATEST_COMPUTER" >> $GITHUB_OUTPUT - echo "som_version=$LATEST_SOM" >> $GITHUB_OUTPUT - echo "core_version=$LATEST_CORE" >> $GITHUB_OUTPUT - - # Determine major version for version constraint - COMPUTER_MAJOR=$(echo $LATEST_COMPUTER | cut -d. -f1) - SOM_MAJOR=$(echo $LATEST_SOM | cut -d. -f1) - CORE_MAJOR=$(echo $LATEST_CORE | cut -d. -f1) - - NEXT_COMPUTER_MAJOR=$((COMPUTER_MAJOR + 1)) - NEXT_SOM_MAJOR=$((SOM_MAJOR + 1)) - NEXT_CORE_MAJOR=$((CORE_MAJOR + 1)) - - # Update dependencies in pyproject.toml - if [[ "$OSTYPE" == "darwin"* ]]; then - # macOS version of sed needs an empty string for -i - sed -i '' "s/\"cua-computer>=.*,<.*\"/\"cua-computer>=$LATEST_COMPUTER,<$NEXT_COMPUTER_MAJOR.0.0\"/" pyproject.toml - sed -i '' "s/\"cua-som>=.*,<.*\"/\"cua-som>=$LATEST_SOM,<$NEXT_SOM_MAJOR.0.0\"/" pyproject.toml - sed -i '' "s/\"cua-core>=.*,<.*\"/\"cua-core>=$LATEST_CORE,<$NEXT_CORE_MAJOR.0.0\"/" pyproject.toml - else - # Linux version - sed -i "s/\"cua-computer>=.*,<.*\"/\"cua-computer>=$LATEST_COMPUTER,<$NEXT_COMPUTER_MAJOR.0.0\"/" pyproject.toml - sed -i "s/\"cua-som>=.*,<.*\"/\"cua-som>=$LATEST_SOM,<$NEXT_SOM_MAJOR.0.0\"/" pyproject.toml - sed -i "s/\"cua-core>=.*,<.*\"/\"cua-core>=$LATEST_CORE,<$NEXT_CORE_MAJOR.0.0\"/" pyproject.toml - fi - - # Display the updated dependencies - echo "Updated dependencies in pyproject.toml:" - grep -E "cua-computer|cua-som|cua-core" pyproject.toml + # Create a more robust Python script for PyPI version checking + cat > get_latest_versions.py << 'EOF' + import requests + import json + import sys + + def get_package_version(package_name, fallback="0.1.0"): + try: + response = requests.get(f'https://pypi.org/pypi/{package_name}/json') + print(f"API Response Status for {package_name}: {response.status_code}", file=sys.stderr) + + if response.status_code != 200: + print(f"API request failed for {package_name}, using fallback version", file=sys.stderr) + return fallback + + data = json.loads(response.text) + + if 'info' not in data: + print(f"Missing 'info' key in API response for {package_name}, using fallback version", file=sys.stderr) + return fallback + + return data['info']['version'] + except Exception as e: + print(f"Error fetching version for {package_name}: {str(e)}", file=sys.stderr) + return fallback + + # Get latest versions + print(get_package_version('cua-computer')) + print(get_package_version('cua-som')) + print(get_package_version('cua-core')) + EOF + + # Execute the script to get the versions + VERSIONS=($(python get_latest_versions.py)) + LATEST_COMPUTER=${VERSIONS[0]} + LATEST_SOM=${VERSIONS[1]} + LATEST_CORE=${VERSIONS[2]} + + echo "Latest cua-computer version: $LATEST_COMPUTER" + echo "Latest cua-som version: $LATEST_SOM" + echo "Latest cua-core version: $LATEST_CORE" + + # Output the versions for the next job + echo "computer_version=$LATEST_COMPUTER" >> $GITHUB_OUTPUT + echo "som_version=$LATEST_SOM" >> $GITHUB_OUTPUT + echo "core_version=$LATEST_CORE" >> $GITHUB_OUTPUT + + # Determine major version for version constraint + COMPUTER_MAJOR=$(echo $LATEST_COMPUTER | cut -d. -f1) + SOM_MAJOR=$(echo $LATEST_SOM | cut -d. -f1) + CORE_MAJOR=$(echo $LATEST_CORE | cut -d. -f1) + + NEXT_COMPUTER_MAJOR=$((COMPUTER_MAJOR + 1)) + NEXT_SOM_MAJOR=$((SOM_MAJOR + 1)) + NEXT_CORE_MAJOR=$((CORE_MAJOR + 1)) + + # Update dependencies in pyproject.toml + if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS version of sed needs an empty string for -i + sed -i '' "s/\"cua-computer>=.*,<.*\"/\"cua-computer>=$LATEST_COMPUTER,<$NEXT_COMPUTER_MAJOR.0.0\"/" pyproject.toml + sed -i '' "s/\"cua-som>=.*,<.*\"/\"cua-som>=$LATEST_SOM,<$NEXT_SOM_MAJOR.0.0\"/" pyproject.toml + sed -i '' "s/\"cua-core>=.*,<.*\"/\"cua-core>=$LATEST_CORE,<$NEXT_CORE_MAJOR.0.0\"/" pyproject.toml + else + # Linux version + sed -i "s/\"cua-computer>=.*,<.*\"/\"cua-computer>=$LATEST_COMPUTER,<$NEXT_COMPUTER_MAJOR.0.0\"/" pyproject.toml + sed -i "s/\"cua-som>=.*,<.*\"/\"cua-som>=$LATEST_SOM,<$NEXT_SOM_MAJOR.0.0\"/" pyproject.toml + sed -i "s/\"cua-core>=.*,<.*\"/\"cua-core>=$LATEST_CORE,<$NEXT_CORE_MAJOR.0.0\"/" pyproject.toml + fi + + # Display the updated dependencies + echo "Updated dependencies in pyproject.toml:" + grep -E "cua-computer|cua-som|cua-core" pyproject.toml publish: needs: prepare uses: ./.github/workflows/reusable-publish.yml with: package_name: "agent" - package_dir: "libs/agent" + package_dir: "libs/python/agent" version: ${{ needs.prepare.outputs.version }} is_lume_package: false base_package_name: "cua-agent" secrets: PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} - + set-env-variables: needs: [prepare, publish] runs-on: macos-latest @@ -159,4 +159,4 @@ jobs: run: | echo "COMPUTER_VERSION=${{ needs.prepare.outputs.computer_version }}" >> $GITHUB_ENV echo "SOM_VERSION=${{ needs.prepare.outputs.som_version }}" >> $GITHUB_ENV - echo "CORE_VERSION=${{ needs.prepare.outputs.core_version }}" >> $GITHUB_ENV \ No newline at end of file + echo "CORE_VERSION=${{ needs.prepare.outputs.core_version }}" >> $GITHUB_ENV diff --git a/.github/workflows/publish-computer-server.yml b/.github/workflows/publish-computer-server.yml index 15eca348..01f61530 100644 --- a/.github/workflows/publish-computer-server.yml +++ b/.github/workflows/publish-computer-server.yml @@ -3,17 +3,17 @@ name: Publish Computer Server Package on: push: tags: - - 'computer-server-v*' + - "computer-server-v*" workflow_dispatch: inputs: version: - description: 'Version to publish (without v prefix)' + description: "Version to publish (without v prefix)" required: true - default: '0.1.0' + default: "0.1.0" workflow_call: inputs: version: - description: 'Version to publish' + description: "Version to publish" required: true type: string outputs: @@ -31,50 +31,50 @@ jobs: outputs: version: ${{ steps.get-version.outputs.version }} steps: - - uses: actions/checkout@v4 - - - name: Determine version - id: get-version - run: | - if [ "${{ github.event_name }}" == "push" ]; then - # Extract version from tag (for package-specific tags) - if [[ "${{ github.ref }}" =~ ^refs/tags/computer-server-v([0-9]+\.[0-9]+\.[0-9]+) ]]; then - VERSION=${BASH_REMATCH[1]} - else - echo "Invalid tag format for computer-server" - exit 1 - fi - elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then - # Use version from workflow dispatch - VERSION=${{ github.event.inputs.version }} - else - # Use version from workflow_call - VERSION=${{ inputs.version }} - fi - echo "VERSION=$VERSION" - echo "version=$VERSION" >> $GITHUB_OUTPUT + - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.10' + - name: Determine version + id: get-version + run: | + if [ "${{ github.event_name }}" == "push" ]; then + # Extract version from tag (for package-specific tags) + if [[ "${{ github.ref }}" =~ ^refs/tags/computer-server-v([0-9]+\.[0-9]+\.[0-9]+) ]]; then + VERSION=${BASH_REMATCH[1]} + else + echo "Invalid tag format for computer-server" + exit 1 + fi + elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + # Use version from workflow dispatch + VERSION=${{ github.event.inputs.version }} + else + # Use version from workflow_call + VERSION=${{ inputs.version }} + fi + echo "VERSION=$VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.10" publish: needs: prepare uses: ./.github/workflows/reusable-publish.yml with: package_name: "computer-server" - package_dir: "libs/computer-server" + package_dir: "libs/python/computer-server" version: ${{ needs.prepare.outputs.version }} is_lume_package: false base_package_name: "cua-computer-server" secrets: PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} - + set-env-variables: needs: [prepare, publish] runs-on: macos-latest steps: - name: Set environment variables for use in other jobs run: | - echo "COMPUTER_VERSION=${{ needs.prepare.outputs.version }}" >> $GITHUB_ENV \ No newline at end of file + echo "COMPUTER_VERSION=${{ needs.prepare.outputs.version }}" >> $GITHUB_ENV diff --git a/.github/workflows/publish-computer.yml b/.github/workflows/publish-computer.yml index 52e5c6ab..df13669d 100644 --- a/.github/workflows/publish-computer.yml +++ b/.github/workflows/publish-computer.yml @@ -3,17 +3,17 @@ name: Publish Computer Package on: push: tags: - - 'computer-v*' + - "computer-v*" workflow_dispatch: inputs: version: - description: 'Version to publish (without v prefix)' + description: "Version to publish (without v prefix)" required: true - default: '0.1.0' + default: "0.1.0" workflow_call: inputs: version: - description: 'Version to publish' + description: "Version to publish" required: true type: string @@ -28,113 +28,113 @@ jobs: version: ${{ steps.get-version.outputs.version }} core_version: ${{ steps.update-deps.outputs.core_version }} steps: - - uses: actions/checkout@v4 - - - name: Determine version - id: get-version - run: | - if [ "${{ github.event_name }}" == "push" ]; then - # Extract version from tag (for package-specific tags) - if [[ "${{ github.ref }}" =~ ^refs/tags/computer-v([0-9]+\.[0-9]+\.[0-9]+) ]]; then - VERSION=${BASH_REMATCH[1]} + - uses: actions/checkout@v4 + + - name: Determine version + id: get-version + run: | + if [ "${{ github.event_name }}" == "push" ]; then + # Extract version from tag (for package-specific tags) + if [[ "${{ github.ref }}" =~ ^refs/tags/computer-v([0-9]+\.[0-9]+\.[0-9]+) ]]; then + VERSION=${BASH_REMATCH[1]} + else + echo "Invalid tag format for computer" + exit 1 + fi + elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + # Use version from workflow dispatch + VERSION=${{ github.event.inputs.version }} else - echo "Invalid tag format for computer" - exit 1 + # Use version from workflow_call + VERSION=${{ inputs.version }} fi - elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then - # Use version from workflow dispatch - VERSION=${{ github.event.inputs.version }} - else - # Use version from workflow_call - VERSION=${{ inputs.version }} - fi - echo "VERSION=$VERSION" - echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "VERSION=$VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.11' + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" - - name: Update dependencies to latest versions - id: update-deps - run: | - cd libs/computer/python - # Install required package for PyPI API access - pip install requests - - # Create a more robust Python script for PyPI version checking - cat > get_latest_versions.py << 'EOF' - import requests - import json - import sys + - name: Update dependencies to latest versions + id: update-deps + run: | + cd libs/python/computer + # Install required package for PyPI API access + pip install requests - def get_package_version(package_name, fallback="0.1.0"): - try: - response = requests.get(f'https://pypi.org/pypi/{package_name}/json') - print(f"API Response Status for {package_name}: {response.status_code}", file=sys.stderr) - - if response.status_code != 200: - print(f"API request failed for {package_name}, using fallback version", file=sys.stderr) - return fallback - - data = json.loads(response.text) - - if 'info' not in data: - print(f"Missing 'info' key in API response for {package_name}, using fallback version", file=sys.stderr) - return fallback - - return data['info']['version'] - except Exception as e: - print(f"Error fetching version for {package_name}: {str(e)}", file=sys.stderr) - return fallback + # Create a more robust Python script for PyPI version checking + cat > get_latest_versions.py << 'EOF' + import requests + import json + import sys - # Get latest versions - print(get_package_version('cua-core')) - EOF - - # Execute the script to get the versions - VERSIONS=($(python get_latest_versions.py)) - LATEST_CORE=${VERSIONS[0]} - - echo "Latest cua-core version: $LATEST_CORE" - - # Output the versions for the next job - echo "core_version=$LATEST_CORE" >> $GITHUB_OUTPUT - - # Determine major version for version constraint - CORE_MAJOR=$(echo $LATEST_CORE | cut -d. -f1) - NEXT_CORE_MAJOR=$((CORE_MAJOR + 1)) - - # Update dependencies in pyproject.toml - if [[ "$OSTYPE" == "darwin"* ]]; then - # macOS version of sed needs an empty string for -i - sed -i '' "s/\"cua-core>=.*,<.*\"/\"cua-core>=$LATEST_CORE,<$NEXT_CORE_MAJOR.0.0\"/" pyproject.toml - else - # Linux version - sed -i "s/\"cua-core>=.*,<.*\"/\"cua-core>=$LATEST_CORE,<$NEXT_CORE_MAJOR.0.0\"/" pyproject.toml - fi - - # Display the updated dependencies - echo "Updated dependencies in pyproject.toml:" - grep -E "cua-core" pyproject.toml + def get_package_version(package_name, fallback="0.1.0"): + try: + response = requests.get(f'https://pypi.org/pypi/{package_name}/json') + print(f"API Response Status for {package_name}: {response.status_code}", file=sys.stderr) + + if response.status_code != 200: + print(f"API request failed for {package_name}, using fallback version", file=sys.stderr) + return fallback + + data = json.loads(response.text) + + if 'info' not in data: + print(f"Missing 'info' key in API response for {package_name}, using fallback version", file=sys.stderr) + return fallback + + return data['info']['version'] + except Exception as e: + print(f"Error fetching version for {package_name}: {str(e)}", file=sys.stderr) + return fallback + + # Get latest versions + print(get_package_version('cua-core')) + EOF + + # Execute the script to get the versions + VERSIONS=($(python get_latest_versions.py)) + LATEST_CORE=${VERSIONS[0]} + + echo "Latest cua-core version: $LATEST_CORE" + + # Output the versions for the next job + echo "core_version=$LATEST_CORE" >> $GITHUB_OUTPUT + + # Determine major version for version constraint + CORE_MAJOR=$(echo $LATEST_CORE | cut -d. -f1) + NEXT_CORE_MAJOR=$((CORE_MAJOR + 1)) + + # Update dependencies in pyproject.toml + if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS version of sed needs an empty string for -i + sed -i '' "s/\"cua-core>=.*,<.*\"/\"cua-core>=$LATEST_CORE,<$NEXT_CORE_MAJOR.0.0\"/" pyproject.toml + else + # Linux version + sed -i "s/\"cua-core>=.*,<.*\"/\"cua-core>=$LATEST_CORE,<$NEXT_CORE_MAJOR.0.0\"/" pyproject.toml + fi + + # Display the updated dependencies + echo "Updated dependencies in pyproject.toml:" + grep -E "cua-core" pyproject.toml publish: needs: prepare uses: ./.github/workflows/reusable-publish.yml with: package_name: "computer" - package_dir: "libs/computer/python" + package_dir: "libs/python/computer" version: ${{ needs.prepare.outputs.version }} is_lume_package: false base_package_name: "cua-computer" secrets: PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} - + set-env-variables: needs: [prepare, publish] runs-on: macos-latest steps: - name: Set environment variables for use in other jobs run: | - echo "CORE_VERSION=${{ needs.prepare.outputs.core_version }}" >> $GITHUB_ENV \ No newline at end of file + echo "CORE_VERSION=${{ needs.prepare.outputs.core_version }}" >> $GITHUB_ENV diff --git a/.github/workflows/publish-core.yml b/.github/workflows/publish-core.yml index 4f868f26..760c055d 100644 --- a/.github/workflows/publish-core.yml +++ b/.github/workflows/publish-core.yml @@ -3,17 +3,17 @@ name: Publish Core Package on: push: tags: - - 'core-v*' + - "core-v*" workflow_dispatch: inputs: version: - description: 'Version to publish (without v prefix)' + description: "Version to publish (without v prefix)" required: true - default: '0.1.0' + default: "0.1.0" workflow_call: inputs: version: - description: 'Version to publish' + description: "Version to publish" required: true type: string @@ -27,37 +27,37 @@ jobs: outputs: version: ${{ steps.get-version.outputs.version }} steps: - - uses: actions/checkout@v4 - - - name: Determine version - id: get-version - run: | - if [ "${{ github.event_name }}" == "push" ]; then - # Extract version from tag (for package-specific tags) - if [[ "${{ github.ref }}" =~ ^refs/tags/core-v([0-9]+\.[0-9]+\.[0-9]+) ]]; then - VERSION=${BASH_REMATCH[1]} + - uses: actions/checkout@v4 + + - name: Determine version + id: get-version + run: | + if [ "${{ github.event_name }}" == "push" ]; then + # Extract version from tag (for package-specific tags) + if [[ "${{ github.ref }}" =~ ^refs/tags/core-v([0-9]+\.[0-9]+\.[0-9]+) ]]; then + VERSION=${BASH_REMATCH[1]} + else + echo "Invalid tag format for core" + exit 1 + fi + elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + # Use version from workflow dispatch + VERSION=${{ github.event.inputs.version }} else - echo "Invalid tag format for core" - exit 1 + # Use version from workflow_call + VERSION=${{ inputs.version }} fi - elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then - # Use version from workflow dispatch - VERSION=${{ github.event.inputs.version }} - else - # Use version from workflow_call - VERSION=${{ inputs.version }} - fi - echo "VERSION=$VERSION" - echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "VERSION=$VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT publish: needs: prepare uses: ./.github/workflows/reusable-publish.yml with: package_name: "core" - package_dir: "libs/core" + package_dir: "libs/python/core" version: ${{ needs.prepare.outputs.version }} is_lume_package: false base_package_name: "cua-core" secrets: - PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} \ No newline at end of file + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} diff --git a/.github/workflows/publish-lume.yml b/.github/workflows/publish-lume.yml index ec5e7550..8557d516 100644 --- a/.github/workflows/publish-lume.yml +++ b/.github/workflows/publish-lume.yml @@ -3,17 +3,17 @@ name: Publish Notarized Lume on: push: tags: - - 'lume-v*' + - "lume-v*" workflow_dispatch: inputs: version: - description: 'Version to notarize (without v prefix)' + description: "Version to notarize (without v prefix)" required: true - default: '0.1.0' + default: "0.1.0" workflow_call: inputs: version: - description: 'Version to notarize' + description: "Version to notarize" required: true type: string secrets: @@ -64,7 +64,7 @@ jobs: - name: Create .release directory run: mkdir -p .release - + - name: Set version id: set_version run: | @@ -82,11 +82,11 @@ jobs: echo "Error: No version found in tag or input" exit 1 fi - + # Update version in Main.swift echo "Updating version in Main.swift to $VERSION" - sed -i '' "s/static let current: String = \".*\"/static let current: String = \"$VERSION\"/" libs/lume/src/Main.swift - + sed -i '' "s/static let current: String = \".*\"/static let current: String = \"$VERSION\"/" libs/swift/lume/src/Main.swift + # Set output for later steps echo "version=$VERSION" >> $GITHUB_OUTPUT @@ -106,34 +106,34 @@ jobs: # Import certificates echo $APPLICATION_CERT_BASE64 | base64 --decode > application.p12 echo $INSTALLER_CERT_BASE64 | base64 --decode > installer.p12 - + # Import certificates silently (minimize output) security import application.p12 -k build.keychain -P "$CERT_PASSWORD" -T /usr/bin/codesign -T /usr/bin/pkgbuild > /dev/null 2>&1 security import installer.p12 -k build.keychain -P "$CERT_PASSWORD" -T /usr/bin/codesign -T /usr/bin/pkgbuild > /dev/null 2>&1 - + # Allow codesign to access the certificates (minimal output) security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain > /dev/null 2>&1 - + # Verify certificates were imported echo "Verifying signing identities..." CERT_COUNT=$(security find-identity -v -p codesigning build.keychain | grep -c "Developer ID Application" || echo "0") INSTALLER_COUNT=$(security find-identity -v build.keychain | grep -c "Developer ID Installer" || echo "0") - + if [ "$CERT_COUNT" -eq 0 ]; then echo "Error: No Developer ID Application certificate found" security find-identity -v -p codesigning build.keychain exit 1 fi - + if [ "$INSTALLER_COUNT" -eq 0 ]; then echo "Error: No Developer ID Installer certificate found" security find-identity -v build.keychain exit 1 fi - + echo "Found $CERT_COUNT Developer ID Application certificate(s) and $INSTALLER_COUNT Developer ID Installer certificate(s)" echo "All required certificates verified successfully" - + # Clean up certificate files rm application.p12 installer.p12 @@ -147,45 +147,45 @@ jobs: CERT_APPLICATION_NAME: "Developer ID Application: ${{ secrets.DEVELOPER_NAME }} (${{ secrets.TEAM_ID }})" CERT_INSTALLER_NAME: "Developer ID Installer: ${{ secrets.DEVELOPER_NAME }} (${{ secrets.TEAM_ID }})" VERSION: ${{ steps.set_version.outputs.version }} - working-directory: ./libs/lume + working-directory: ./libs/swift/lume run: | # Minimal debug information echo "Starting build process..." echo "Swift version: $(swift --version | head -n 1)" echo "Building version: $VERSION" - + # Ensure .release directory exists mkdir -p .release chmod 755 .release - + # Build the project first (redirect verbose output) echo "Building project..." swift build --configuration release > build.log 2>&1 echo "Build completed." - + # Run the notarization script with LOG_LEVEL env var chmod +x scripts/build/build-release-notarized.sh cd scripts/build LOG_LEVEL=minimal ./build-release-notarized.sh - + # Return to the lume directory cd ../.. - + # Debug: List what files were actually created echo "Files in .release directory:" find .release -type f -name "*.tar.gz" -o -name "*.pkg.tar.gz" - + # Get architecture for output filename ARCH=$(uname -m) OS_IDENTIFIER="darwin-${ARCH}" - + # Output paths for later use echo "tarball_path=.release/lume-${VERSION}-${OS_IDENTIFIER}.tar.gz" >> $GITHUB_OUTPUT echo "pkg_path=.release/lume-${VERSION}-${OS_IDENTIFIER}.pkg.tar.gz" >> $GITHUB_OUTPUT - name: Generate SHA256 Checksums id: generate_checksums - working-directory: ./libs/lume/.release + working-directory: ./libs/swift/lume/.release run: | # Use existing checksums file if it exists, otherwise generate one if [ -f "checksums.txt" ]; then @@ -197,31 +197,31 @@ jobs: shasum -a 256 lume-*.tar.gz >> checksums.txt echo '```' >> checksums.txt fi - + checksums=$(cat checksums.txt) echo "checksums<> $GITHUB_OUTPUT echo "$checksums" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - + # Debug: Show all files in the release directory echo "All files in release directory:" ls -la - name: Create Standard Version Releases - working-directory: ./libs/lume/.release + working-directory: ./libs/swift/lume/.release run: | VERSION=${{ steps.set_version.outputs.version }} ARCH=$(uname -m) OS_IDENTIFIER="darwin-${ARCH}" - + # Create OS-tagged symlinks ln -sf "lume-${VERSION}-${OS_IDENTIFIER}.tar.gz" "lume-darwin.tar.gz" ln -sf "lume-${VERSION}-${OS_IDENTIFIER}.pkg.tar.gz" "lume-darwin.pkg.tar.gz" - + # Create simple symlinks ln -sf "lume-${VERSION}-${OS_IDENTIFIER}.tar.gz" "lume.tar.gz" ln -sf "lume-${VERSION}-${OS_IDENTIFIER}.pkg.tar.gz" "lume.pkg.tar.gz" - + # List all files (including symlinks) echo "Files with symlinks in release directory:" ls -la @@ -230,14 +230,14 @@ jobs: uses: actions/upload-artifact@v4 with: name: lume-notarized-tarball - path: ./libs/lume/${{ steps.build_notarize.outputs.tarball_path }} + path: ./libs/swift/lume/${{ steps.build_notarize.outputs.tarball_path }} if-no-files-found: error - name: Upload Notarized Package (Installer) uses: actions/upload-artifact@v4 with: name: lume-notarized-installer - path: ./libs/lume/${{ steps.build_notarize.outputs.pkg_path }} + path: ./libs/swift/lume/${{ steps.build_notarize.outputs.pkg_path }} if-no-files-found: error - name: Create Release @@ -245,18 +245,18 @@ jobs: uses: softprops/action-gh-release@v1 with: files: | - ./libs/lume/${{ steps.build_notarize.outputs.tarball_path }} - ./libs/lume/${{ steps.build_notarize.outputs.pkg_path }} - ./libs/lume/.release/lume-darwin.tar.gz - ./libs/lume/.release/lume-darwin.pkg.tar.gz - ./libs/lume/.release/lume.tar.gz - ./libs/lume/.release/lume.pkg.tar.gz + ./libs/swift/lume/${{ steps.build_notarize.outputs.tarball_path }} + ./libs/swift/lume/${{ steps.build_notarize.outputs.pkg_path }} + ./libs/swift/lume/.release/lume-darwin.tar.gz + ./libs/swift/lume/.release/lume-darwin.pkg.tar.gz + ./libs/swift/lume/.release/lume.tar.gz + ./libs/swift/lume/.release/lume.pkg.tar.gz body: | ${{ steps.generate_checksums.outputs.checksums }} - + ### Installation with script - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/libs/lume/scripts/install.sh)" + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/libs/swift/lume/scripts/install.sh)" ``` generate_release_notes: true - make_latest: true \ No newline at end of file + make_latest: true diff --git a/.github/workflows/publish-mcp-server.yml b/.github/workflows/publish-mcp-server.yml index e6eccd5a..0e84a5a6 100644 --- a/.github/workflows/publish-mcp-server.yml +++ b/.github/workflows/publish-mcp-server.yml @@ -3,17 +3,17 @@ name: Publish MCP Server Package on: push: tags: - - 'mcp-server-v*' + - "mcp-server-v*" workflow_dispatch: inputs: version: - description: 'Version to publish (without v prefix)' + description: "Version to publish (without v prefix)" required: true - default: '0.1.0' + default: "0.1.0" workflow_call: inputs: version: - description: 'Version to publish' + description: "Version to publish" required: true type: string outputs: @@ -33,120 +33,120 @@ jobs: agent_version: ${{ steps.update-deps.outputs.agent_version }} computer_version: ${{ steps.update-deps.outputs.computer_version }} steps: - - uses: actions/checkout@v4 - - - name: Determine version - id: get-version - run: | - if [ "${{ github.event_name }}" == "push" ]; then - # Extract version from tag (for package-specific tags) - if [[ "${{ github.ref }}" =~ ^refs/tags/mcp-server-v([0-9]+\.[0-9]+\.[0-9]+) ]]; then - VERSION=${BASH_REMATCH[1]} + - uses: actions/checkout@v4 + + - name: Determine version + id: get-version + run: | + if [ "${{ github.event_name }}" == "push" ]; then + # Extract version from tag (for package-specific tags) + if [[ "${{ github.ref }}" =~ ^refs/tags/mcp-server-v([0-9]+\.[0-9]+\.[0-9]+) ]]; then + VERSION=${BASH_REMATCH[1]} + else + echo "Invalid tag format for mcp-server" + exit 1 + fi + elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + # Use version from workflow dispatch + VERSION=${{ github.event.inputs.version }} else - echo "Invalid tag format for mcp-server" - exit 1 + # Use version from workflow_call + VERSION=${{ inputs.version }} fi - elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then - # Use version from workflow dispatch - VERSION=${{ github.event.inputs.version }} - else - # Use version from workflow_call - VERSION=${{ inputs.version }} - fi - echo "VERSION=$VERSION" - echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "VERSION=$VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.11' + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" - - name: Update dependencies to latest versions - id: update-deps - run: | - cd libs/mcp-server - - # Install required package for PyPI API access - pip install requests - - # Create a Python script for PyPI version checking - cat > get_latest_versions.py << 'EOF' - import requests - import json - import sys + - name: Update dependencies to latest versions + id: update-deps + run: | + cd libs/python/mcp-server - def get_package_version(package_name, fallback="0.1.0"): - try: - response = requests.get(f'https://pypi.org/pypi/{package_name}/json') - print(f"API Response Status for {package_name}: {response.status_code}", file=sys.stderr) - - if response.status_code != 200: - print(f"API request failed for {package_name}, using fallback version", file=sys.stderr) - return fallback - - data = json.loads(response.text) - - if 'info' not in data: - print(f"Missing 'info' key in API response for {package_name}, using fallback version", file=sys.stderr) - return fallback - - return data['info']['version'] - except Exception as e: - print(f"Error fetching version for {package_name}: {str(e)}", file=sys.stderr) - return fallback + # Install required package for PyPI API access + pip install requests - # Get latest versions - print(get_package_version('cua-agent')) - print(get_package_version('cua-computer')) - EOF - - # Execute the script to get the versions - VERSIONS=($(python get_latest_versions.py)) - LATEST_AGENT=${VERSIONS[0]} - LATEST_COMPUTER=${VERSIONS[1]} - - echo "Latest cua-agent version: $LATEST_AGENT" - echo "Latest cua-computer version: $LATEST_COMPUTER" - - # Output the versions for the next job - echo "agent_version=$LATEST_AGENT" >> $GITHUB_OUTPUT - echo "computer_version=$LATEST_COMPUTER" >> $GITHUB_OUTPUT - - # Determine major version for version constraint - AGENT_MAJOR=$(echo $LATEST_AGENT | cut -d. -f1) - COMPUTER_MAJOR=$(echo $LATEST_COMPUTER | cut -d. -f1) - - NEXT_AGENT_MAJOR=$((AGENT_MAJOR + 1)) - NEXT_COMPUTER_MAJOR=$((COMPUTER_MAJOR + 1)) - - # Update dependencies in pyproject.toml - if [[ "$OSTYPE" == "darwin"* ]]; then - # macOS version of sed needs an empty string for -i - # Update cua-agent with all extras - sed -i '' "s/\"cua-agent\[all\]>=.*,<.*\"/\"cua-agent[all]>=$LATEST_AGENT,<$NEXT_AGENT_MAJOR.0.0\"/" pyproject.toml - sed -i '' "s/\"cua-computer>=.*,<.*\"/\"cua-computer>=$LATEST_COMPUTER,<$NEXT_COMPUTER_MAJOR.0.0\"/" pyproject.toml - else - # Linux version - sed -i "s/\"cua-agent\[all\]>=.*,<.*\"/\"cua-agent[all]>=$LATEST_AGENT,<$NEXT_AGENT_MAJOR.0.0\"/" pyproject.toml - sed -i "s/\"cua-computer>=.*,<.*\"/\"cua-computer>=$LATEST_COMPUTER,<$NEXT_COMPUTER_MAJOR.0.0\"/" pyproject.toml - fi - - # Display the updated dependencies - echo "Updated dependencies in pyproject.toml:" - grep -E "cua-agent|cua-computer" pyproject.toml + # Create a Python script for PyPI version checking + cat > get_latest_versions.py << 'EOF' + import requests + import json + import sys + + def get_package_version(package_name, fallback="0.1.0"): + try: + response = requests.get(f'https://pypi.org/pypi/{package_name}/json') + print(f"API Response Status for {package_name}: {response.status_code}", file=sys.stderr) + + if response.status_code != 200: + print(f"API request failed for {package_name}, using fallback version", file=sys.stderr) + return fallback + + data = json.loads(response.text) + + if 'info' not in data: + print(f"Missing 'info' key in API response for {package_name}, using fallback version", file=sys.stderr) + return fallback + + return data['info']['version'] + except Exception as e: + print(f"Error fetching version for {package_name}: {str(e)}", file=sys.stderr) + return fallback + + # Get latest versions + print(get_package_version('cua-agent')) + print(get_package_version('cua-computer')) + EOF + + # Execute the script to get the versions + VERSIONS=($(python get_latest_versions.py)) + LATEST_AGENT=${VERSIONS[0]} + LATEST_COMPUTER=${VERSIONS[1]} + + echo "Latest cua-agent version: $LATEST_AGENT" + echo "Latest cua-computer version: $LATEST_COMPUTER" + + # Output the versions for the next job + echo "agent_version=$LATEST_AGENT" >> $GITHUB_OUTPUT + echo "computer_version=$LATEST_COMPUTER" >> $GITHUB_OUTPUT + + # Determine major version for version constraint + AGENT_MAJOR=$(echo $LATEST_AGENT | cut -d. -f1) + COMPUTER_MAJOR=$(echo $LATEST_COMPUTER | cut -d. -f1) + + NEXT_AGENT_MAJOR=$((AGENT_MAJOR + 1)) + NEXT_COMPUTER_MAJOR=$((COMPUTER_MAJOR + 1)) + + # Update dependencies in pyproject.toml + if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS version of sed needs an empty string for -i + # Update cua-agent with all extras + sed -i '' "s/\"cua-agent\[all\]>=.*,<.*\"/\"cua-agent[all]>=$LATEST_AGENT,<$NEXT_AGENT_MAJOR.0.0\"/" pyproject.toml + sed -i '' "s/\"cua-computer>=.*,<.*\"/\"cua-computer>=$LATEST_COMPUTER,<$NEXT_COMPUTER_MAJOR.0.0\"/" pyproject.toml + else + # Linux version + sed -i "s/\"cua-agent\[all\]>=.*,<.*\"/\"cua-agent[all]>=$LATEST_AGENT,<$NEXT_AGENT_MAJOR.0.0\"/" pyproject.toml + sed -i "s/\"cua-computer>=.*,<.*\"/\"cua-computer>=$LATEST_COMPUTER,<$NEXT_COMPUTER_MAJOR.0.0\"/" pyproject.toml + fi + + # Display the updated dependencies + echo "Updated dependencies in pyproject.toml:" + grep -E "cua-agent|cua-computer" pyproject.toml publish: needs: prepare uses: ./.github/workflows/reusable-publish.yml with: package_name: "mcp-server" - package_dir: "libs/mcp-server" + package_dir: "libs/python/mcp-server" version: ${{ needs.prepare.outputs.version }} is_lume_package: false base_package_name: "cua-mcp-server" secrets: PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} - + set-env-variables: needs: [prepare, publish] runs-on: macos-latest @@ -154,4 +154,4 @@ jobs: - name: Set environment variables for use in other jobs run: | echo "AGENT_VERSION=${{ needs.prepare.outputs.agent_version }}" >> $GITHUB_ENV - echo "COMPUTER_VERSION=${{ needs.prepare.outputs.computer_version }}" >> $GITHUB_ENV \ No newline at end of file + echo "COMPUTER_VERSION=${{ needs.prepare.outputs.computer_version }}" >> $GITHUB_ENV diff --git a/.github/workflows/publish-pylume.yml b/.github/workflows/publish-pylume.yml index c5bd4f6f..e9e52539 100644 --- a/.github/workflows/publish-pylume.yml +++ b/.github/workflows/publish-pylume.yml @@ -3,17 +3,17 @@ name: Publish Pylume Package on: push: tags: - - 'pylume-v*' + - "pylume-v*" workflow_dispatch: inputs: version: - description: 'Version to publish (without v prefix)' + description: "Version to publish (without v prefix)" required: true - default: '0.1.0' + default: "0.1.0" workflow_call: inputs: version: - description: 'Version to publish' + description: "Version to publish" required: true type: string outputs: @@ -31,52 +31,52 @@ jobs: outputs: version: ${{ steps.get-version.outputs.version }} steps: - - uses: actions/checkout@v4 - - - name: Determine version - id: get-version - run: | - if [ "${{ github.event_name }}" == "push" ]; then - # Extract version from tag (for package-specific tags) - if [[ "${{ github.ref }}" =~ ^refs/tags/pylume-v([0-9]+\.[0-9]+\.[0-9]+) ]]; then - VERSION=${BASH_REMATCH[1]} + - uses: actions/checkout@v4 + + - name: Determine version + id: get-version + run: | + if [ "${{ github.event_name }}" == "push" ]; then + # Extract version from tag (for package-specific tags) + if [[ "${{ github.ref }}" =~ ^refs/tags/pylume-v([0-9]+\.[0-9]+\.[0-9]+) ]]; then + VERSION=${BASH_REMATCH[1]} + else + echo "Invalid tag format for pylume" + exit 1 + fi + elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + # Use version from workflow dispatch + VERSION=${{ github.event.inputs.version }} else - echo "Invalid tag format for pylume" - exit 1 + # Use version from workflow_call + VERSION=${{ inputs.version }} fi - elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then - # Use version from workflow dispatch - VERSION=${{ github.event.inputs.version }} - else - # Use version from workflow_call - VERSION=${{ inputs.version }} - fi - echo "VERSION=$VERSION" - echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "VERSION=$VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT validate-version: runs-on: macos-latest needs: determine-version steps: - - uses: actions/checkout@v4 - - name: Validate version - id: validate-version - run: | - CODE_VERSION=$(grep '__version__' libs/pylume/pylume/__init__.py | cut -d'"' -f2) - if [ "${{ needs.determine-version.outputs.version }}" != "$CODE_VERSION" ]; then - echo "Version mismatch: expected $CODE_VERSION, got ${{ needs.determine-version.outputs.version }}" - exit 1 - fi - echo "Version validated: $CODE_VERSION" + - uses: actions/checkout@v4 + - name: Validate version + id: validate-version + run: | + CODE_VERSION=$(grep '__version__' libs/python/pylume/pylume/__init__.py | cut -d'"' -f2) + if [ "${{ needs.determine-version.outputs.version }}" != "$CODE_VERSION" ]; then + echo "Version mismatch: expected $CODE_VERSION, got ${{ needs.determine-version.outputs.version }}" + exit 1 + fi + echo "Version validated: $CODE_VERSION" publish: needs: determine-version uses: ./.github/workflows/reusable-publish.yml with: package_name: "pylume" - package_dir: "libs/pylume" + package_dir: "libs/python/pylume" version: ${{ needs.determine-version.outputs.version }} is_lume_package: true base_package_name: "pylume" secrets: - PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} \ No newline at end of file + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} diff --git a/.github/workflows/publish-som.yml b/.github/workflows/publish-som.yml index b1d53ac8..a5ac30e9 100644 --- a/.github/workflows/publish-som.yml +++ b/.github/workflows/publish-som.yml @@ -3,17 +3,17 @@ name: Publish SOM Package on: push: tags: - - 'som-v*' + - "som-v*" workflow_dispatch: inputs: version: - description: 'Version to publish (without v prefix)' + description: "Version to publish (without v prefix)" required: true - default: '0.1.0' + default: "0.1.0" workflow_call: inputs: version: - description: 'Version to publish' + description: "Version to publish" required: true type: string outputs: @@ -31,37 +31,37 @@ jobs: outputs: version: ${{ steps.get-version.outputs.version }} steps: - - uses: actions/checkout@v4 - - - name: Determine version - id: get-version - run: | - if [ "${{ github.event_name }}" == "push" ]; then - # Extract version from tag (for package-specific tags) - if [[ "${{ github.ref }}" =~ ^refs/tags/som-v([0-9]+\.[0-9]+\.[0-9]+) ]]; then - VERSION=${BASH_REMATCH[1]} + - uses: actions/checkout@v4 + + - name: Determine version + id: get-version + run: | + if [ "${{ github.event_name }}" == "push" ]; then + # Extract version from tag (for package-specific tags) + if [[ "${{ github.ref }}" =~ ^refs/tags/som-v([0-9]+\.[0-9]+\.[0-9]+) ]]; then + VERSION=${BASH_REMATCH[1]} + else + echo "Invalid tag format for som" + exit 1 + fi + elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then + # Use version from workflow dispatch + VERSION=${{ github.event.inputs.version }} else - echo "Invalid tag format for som" - exit 1 + # Use version from workflow_call + VERSION=${{ inputs.version }} fi - elif [ "${{ github.event_name }}" == "workflow_dispatch" ]; then - # Use version from workflow dispatch - VERSION=${{ github.event.inputs.version }} - else - # Use version from workflow_call - VERSION=${{ inputs.version }} - fi - echo "VERSION=$VERSION" - echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "VERSION=$VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT publish: needs: determine-version uses: ./.github/workflows/reusable-publish.yml with: package_name: "som" - package_dir: "libs/som" + package_dir: "libs/python/som" version: ${{ needs.determine-version.outputs.version }} is_lume_package: false base_package_name: "cua-som" secrets: - PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} \ No newline at end of file + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} diff --git a/.github/workflows/reusable-publish.yml b/.github/workflows/reusable-publish.yml index 8856b60d..f1eb045e 100644 --- a/.github/workflows/reusable-publish.yml +++ b/.github/workflows/reusable-publish.yml @@ -4,28 +4,28 @@ on: workflow_call: inputs: package_name: - description: 'Name of the package (e.g. pylume, computer, agent)' + description: "Name of the package (e.g. pylume, computer, agent)" required: true type: string package_dir: - description: 'Directory containing the package relative to workspace root (e.g. libs/pylume)' + description: "Directory containing the package relative to workspace root (e.g. libs/python/pylume)" required: true type: string version: - description: 'Version to publish' + description: "Version to publish" required: true type: string is_lume_package: - description: 'Whether this package includes the lume binary' + description: "Whether this package includes the lume binary" required: false type: boolean default: false base_package_name: - description: 'PyPI package name (e.g. pylume, cua-agent)' + description: "PyPI package name (e.g. pylume, cua-agent)" required: true type: string make_latest: - description: 'Whether to mark this release as latest (should only be true for lume)' + description: "Whether to mark this release as latest (should only be true for lume)" required: false type: boolean default: false @@ -41,240 +41,240 @@ jobs: build-and-publish: runs-on: macos-latest permissions: - contents: write # This permission is needed for creating releases + contents: write # This permission is needed for creating releases outputs: version: ${{ steps.set-version.outputs.version }} steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Full history for release creation - - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.11' - - - name: Create root pdm.lock file - run: | - # Create an empty pdm.lock file in the root - touch pdm.lock - - - name: Install PDM - uses: pdm-project/setup-pdm@v3 - with: - python-version: '3.11' - cache: true - - - name: Set version - id: set-version - run: | - echo "VERSION=${{ inputs.version }}" >> $GITHUB_ENV - echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # Full history for release creation - - name: Initialize PDM in package directory - run: | - # Make sure we're working with a properly initialized PDM project - cd ${{ inputs.package_dir }} - - # Create pdm.lock if it doesn't exist - if [ ! -f "pdm.lock" ]; then - echo "No pdm.lock found, initializing PDM project..." - pdm lock - fi - - - name: Set version in package - run: | - cd ${{ inputs.package_dir }} - # Replace pdm bump with direct edit of pyproject.toml - if [[ "$OSTYPE" == "darwin"* ]]; then - # macOS version of sed needs an empty string for -i - sed -i '' "s/version = \".*\"/version = \"$VERSION\"/" pyproject.toml - else - # Linux version - sed -i "s/version = \".*\"/version = \"$VERSION\"/" pyproject.toml - fi - # Verify version was updated - echo "Updated version in pyproject.toml:" - grep "version =" pyproject.toml - - # Conditional step for lume binary download (only for pylume package) - - name: Download and setup lume binary - if: inputs.is_lume_package - run: | - # Create a temporary directory for extraction - mkdir -p temp_lume - - # Download the latest lume release directly - echo "Downloading latest lume version..." - curl -sL "https://github.com/trycua/lume/releases/latest/download/lume.tar.gz" -o temp_lume/lume.tar.gz - - # Extract the tar file (ignore ownership and suppress warnings) - cd temp_lume && tar --no-same-owner -xzf lume.tar.gz - - # Make the binary executable - chmod +x lume - - # Copy the lume binary to the correct location in the pylume package - mkdir -p "${GITHUB_WORKSPACE}/${{ inputs.package_dir }}/pylume" - cp lume "${GITHUB_WORKSPACE}/${{ inputs.package_dir }}/pylume/lume" - - # Verify the binary exists and is executable - test -x "${GITHUB_WORKSPACE}/${{ inputs.package_dir }}/pylume/lume" || { echo "lume binary not found or not executable"; exit 1; } - - # Get the version from the downloaded binary for reference - LUME_VERSION=$(./lume --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || echo "unknown") - echo "Using lume version: $LUME_VERSION" - - # Cleanup - cd "${GITHUB_WORKSPACE}" && rm -rf temp_lume - - # Save the lume version for reference - echo "LUME_VERSION=${LUME_VERSION}" >> $GITHUB_ENV - - - name: Build and publish - env: - PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} - run: | - cd ${{ inputs.package_dir }} - # Build with PDM - pdm build - - # For pylume package, verify the binary is in the wheel - if [ "${{ inputs.is_lume_package }}" = "true" ]; then - python -m pip install wheel - wheel unpack dist/*.whl --dest temp_wheel - echo "Listing contents of wheel directory:" - find temp_wheel -type f - test -f temp_wheel/pylume-*/pylume/lume || { echo "lume binary not found in wheel"; exit 1; } - rm -rf temp_wheel - echo "Publishing ${{ inputs.base_package_name }} ${VERSION} with lume ${LUME_VERSION}" - else - echo "Publishing ${{ inputs.base_package_name }} ${VERSION}" - fi - - # Install and use twine directly instead of PDM publish - echo "Installing twine for direct publishing..." - pip install twine - - echo "Publishing to PyPI using twine..." - TWINE_USERNAME="__token__" TWINE_PASSWORD="$PYPI_TOKEN" python -m twine upload dist/* - - # Save the wheel file path for the release - WHEEL_FILE=$(ls dist/*.whl | head -1) - echo "WHEEL_FILE=${WHEEL_FILE}" >> $GITHUB_ENV - - - name: Prepare Simple Release Notes - if: startsWith(github.ref, 'refs/tags/') - run: | - # Create release notes based on package type - echo "# ${{ inputs.base_package_name }} v${VERSION}" > release_notes.md - echo "" >> release_notes.md - - if [ "${{ inputs.package_name }}" = "pylume" ]; then - echo "## Python SDK for lume - run macOS and Linux VMs on Apple Silicon" >> release_notes.md + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Create root pdm.lock file + run: | + # Create an empty pdm.lock file in the root + touch pdm.lock + + - name: Install PDM + uses: pdm-project/setup-pdm@v3 + with: + python-version: "3.11" + cache: true + + - name: Set version + id: set-version + run: | + echo "VERSION=${{ inputs.version }}" >> $GITHUB_ENV + echo "version=${{ inputs.version }}" >> $GITHUB_OUTPUT + + - name: Initialize PDM in package directory + run: | + # Make sure we're working with a properly initialized PDM project + cd ${{ inputs.package_dir }} + + # Create pdm.lock if it doesn't exist + if [ ! -f "pdm.lock" ]; then + echo "No pdm.lock found, initializing PDM project..." + pdm lock + fi + + - name: Set version in package + run: | + cd ${{ inputs.package_dir }} + # Replace pdm bump with direct edit of pyproject.toml + if [[ "$OSTYPE" == "darwin"* ]]; then + # macOS version of sed needs an empty string for -i + sed -i '' "s/version = \".*\"/version = \"$VERSION\"/" pyproject.toml + else + # Linux version + sed -i "s/version = \".*\"/version = \"$VERSION\"/" pyproject.toml + fi + # Verify version was updated + echo "Updated version in pyproject.toml:" + grep "version =" pyproject.toml + + # Conditional step for lume binary download (only for pylume package) + - name: Download and setup lume binary + if: inputs.is_lume_package + run: | + # Create a temporary directory for extraction + mkdir -p temp_lume + + # Download the latest lume release directly + echo "Downloading latest lume version..." + curl -sL "https://github.com/trycua/lume/releases/latest/download/lume.tar.gz" -o temp_lume/lume.tar.gz + + # Extract the tar file (ignore ownership and suppress warnings) + cd temp_lume && tar --no-same-owner -xzf lume.tar.gz + + # Make the binary executable + chmod +x lume + + # Copy the lume binary to the correct location in the pylume package + mkdir -p "${GITHUB_WORKSPACE}/${{ inputs.package_dir }}/pylume" + cp lume "${GITHUB_WORKSPACE}/${{ inputs.package_dir }}/pylume/lume" + + # Verify the binary exists and is executable + test -x "${GITHUB_WORKSPACE}/${{ inputs.package_dir }}/pylume/lume" || { echo "lume binary not found or not executable"; exit 1; } + + # Get the version from the downloaded binary for reference + LUME_VERSION=$(./lume --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || echo "unknown") + echo "Using lume version: $LUME_VERSION" + + # Cleanup + cd "${GITHUB_WORKSPACE}" && rm -rf temp_lume + + # Save the lume version for reference + echo "LUME_VERSION=${LUME_VERSION}" >> $GITHUB_ENV + + - name: Build and publish + env: + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + run: | + cd ${{ inputs.package_dir }} + # Build with PDM + pdm build + + # For pylume package, verify the binary is in the wheel + if [ "${{ inputs.is_lume_package }}" = "true" ]; then + python -m pip install wheel + wheel unpack dist/*.whl --dest temp_wheel + echo "Listing contents of wheel directory:" + find temp_wheel -type f + test -f temp_wheel/pylume-*/pylume/lume || { echo "lume binary not found in wheel"; exit 1; } + rm -rf temp_wheel + echo "Publishing ${{ inputs.base_package_name }} ${VERSION} with lume ${LUME_VERSION}" + else + echo "Publishing ${{ inputs.base_package_name }} ${VERSION}" + fi + + # Install and use twine directly instead of PDM publish + echo "Installing twine for direct publishing..." + pip install twine + + echo "Publishing to PyPI using twine..." + TWINE_USERNAME="__token__" TWINE_PASSWORD="$PYPI_TOKEN" python -m twine upload dist/* + + # Save the wheel file path for the release + WHEEL_FILE=$(ls dist/*.whl | head -1) + echo "WHEEL_FILE=${WHEEL_FILE}" >> $GITHUB_ENV + + - name: Prepare Simple Release Notes + if: startsWith(github.ref, 'refs/tags/') + run: | + # Create release notes based on package type + echo "# ${{ inputs.base_package_name }} v${VERSION}" > release_notes.md echo "" >> release_notes.md - echo "This package provides Python bindings for the lume virtualization tool." >> release_notes.md - echo "" >> release_notes.md - echo "## Dependencies" >> release_notes.md - echo "* lume binary: v${LUME_VERSION}" >> release_notes.md - elif [ "${{ inputs.package_name }}" = "computer" ]; then - echo "## Computer control library for the Computer Universal Automation (CUA) project" >> release_notes.md - echo "" >> release_notes.md - echo "## Dependencies" >> release_notes.md - echo "* pylume: ${PYLUME_VERSION:-latest}" >> release_notes.md - elif [ "${{ inputs.package_name }}" = "agent" ]; then - echo "## Dependencies" >> release_notes.md - echo "* cua-computer: ${COMPUTER_VERSION:-latest}" >> release_notes.md - echo "* cua-som: ${SOM_VERSION:-latest}" >> release_notes.md - echo "" >> release_notes.md - echo "## Installation Options" >> release_notes.md - echo "" >> release_notes.md - echo "### Basic installation with Anthropic" >> release_notes.md - echo '```bash' >> release_notes.md - echo "pip install cua-agent[anthropic]==${VERSION}" >> release_notes.md - echo '```' >> release_notes.md - echo "" >> release_notes.md - echo "### With SOM (recommended)" >> release_notes.md - echo '```bash' >> release_notes.md - echo "pip install cua-agent[som]==${VERSION}" >> release_notes.md - echo '```' >> release_notes.md - echo "" >> release_notes.md - echo "### All features" >> release_notes.md - echo '```bash' >> release_notes.md - echo "pip install cua-agent[all]==${VERSION}" >> release_notes.md - echo '```' >> release_notes.md - elif [ "${{ inputs.package_name }}" = "som" ]; then - echo "## Computer Vision and OCR library for detecting and analyzing UI elements" >> release_notes.md - echo "" >> release_notes.md - echo "This package provides enhanced UI understanding capabilities through computer vision and OCR." >> release_notes.md - elif [ "${{ inputs.package_name }}" = "computer-server" ]; then - echo "## Computer Server for the Computer Universal Automation (CUA) project" >> release_notes.md - echo "" >> release_notes.md - echo "A FastAPI-based server implementation for computer control." >> release_notes.md - echo "" >> release_notes.md - echo "## Dependencies" >> release_notes.md - echo "* cua-computer: ${COMPUTER_VERSION:-latest}" >> release_notes.md - echo "" >> release_notes.md - echo "## Usage" >> release_notes.md - echo '```bash' >> release_notes.md - echo "# Run the server" >> release_notes.md - echo "cua-computer-server" >> release_notes.md - echo '```' >> release_notes.md - elif [ "${{ inputs.package_name }}" = "mcp-server" ]; then - echo "## MCP Server for the Computer-Use Agent (CUA)" >> release_notes.md - echo "" >> release_notes.md - echo "This package provides MCP (Model Context Protocol) integration for CUA agents, allowing them to be used with Claude Desktop, Cursor, and other MCP clients." >> release_notes.md - echo "" >> release_notes.md - echo "## Dependencies" >> release_notes.md - echo "* cua-computer: ${COMPUTER_VERSION:-latest}" >> release_notes.md - echo "* cua-agent: ${AGENT_VERSION:-latest}" >> release_notes.md - echo "" >> release_notes.md - echo "## Usage" >> release_notes.md - echo '```bash' >> release_notes.md - echo "# Run the MCP server directly" >> release_notes.md - echo "cua-mcp-server" >> release_notes.md - echo '```' >> release_notes.md - echo "" >> release_notes.md - echo "## Claude Desktop Integration" >> release_notes.md - echo "Add to your Claude Desktop configuration (~/.config/claude-desktop/claude_desktop_config.json or OS-specific location):" >> release_notes.md - echo '```json' >> release_notes.md - echo '"mcpServers": {' >> release_notes.md - echo ' "cua-agent": {' >> release_notes.md - echo ' "command": "cua-mcp-server",' >> release_notes.md - echo ' "args": [],' >> release_notes.md - echo ' "env": {' >> release_notes.md - echo ' "CUA_AGENT_LOOP": "OMNI",' >> release_notes.md - echo ' "CUA_MODEL_PROVIDER": "ANTHROPIC",' >> release_notes.md - echo ' "CUA_MODEL_NAME": "claude-3-opus-20240229",' >> release_notes.md - echo ' "ANTHROPIC_API_KEY": "your-api-key",' >> release_notes.md - echo ' "PYTHONIOENCODING": "utf-8"' >> release_notes.md - echo ' }' >> release_notes.md - echo ' }' >> release_notes.md - echo '}' >> release_notes.md - echo '```' >> release_notes.md - fi - - # Add installation section if not agent (which has its own installation section) - if [ "${{ inputs.package_name }}" != "agent" ]; then - echo "" >> release_notes.md - echo "## Installation" >> release_notes.md - echo '```bash' >> release_notes.md - echo "pip install ${{ inputs.base_package_name }}==${VERSION}" >> release_notes.md - echo '```' >> release_notes.md - fi - - echo "Release notes created:" - cat release_notes.md - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - if: startsWith(github.ref, 'refs/tags/') - with: - name: "${{ inputs.base_package_name }} v${{ env.VERSION }}" - body_path: release_notes.md - files: ${{ inputs.package_dir }}/${{ env.WHEEL_FILE }} - draft: false - prerelease: false - make_latest: ${{ inputs.package_name == 'lume' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + + if [ "${{ inputs.package_name }}" = "pylume" ]; then + echo "## Python SDK for lume - run macOS and Linux VMs on Apple Silicon" >> release_notes.md + echo "" >> release_notes.md + echo "This package provides Python bindings for the lume virtualization tool." >> release_notes.md + echo "" >> release_notes.md + echo "## Dependencies" >> release_notes.md + echo "* lume binary: v${LUME_VERSION}" >> release_notes.md + elif [ "${{ inputs.package_name }}" = "computer" ]; then + echo "## Computer control library for the Computer Universal Automation (CUA) project" >> release_notes.md + echo "" >> release_notes.md + echo "## Dependencies" >> release_notes.md + echo "* pylume: ${PYLUME_VERSION:-latest}" >> release_notes.md + elif [ "${{ inputs.package_name }}" = "agent" ]; then + echo "## Dependencies" >> release_notes.md + echo "* cua-computer: ${COMPUTER_VERSION:-latest}" >> release_notes.md + echo "* cua-som: ${SOM_VERSION:-latest}" >> release_notes.md + echo "" >> release_notes.md + echo "## Installation Options" >> release_notes.md + echo "" >> release_notes.md + echo "### Basic installation with Anthropic" >> release_notes.md + echo '```bash' >> release_notes.md + echo "pip install cua-agent[anthropic]==${VERSION}" >> release_notes.md + echo '```' >> release_notes.md + echo "" >> release_notes.md + echo "### With SOM (recommended)" >> release_notes.md + echo '```bash' >> release_notes.md + echo "pip install cua-agent[som]==${VERSION}" >> release_notes.md + echo '```' >> release_notes.md + echo "" >> release_notes.md + echo "### All features" >> release_notes.md + echo '```bash' >> release_notes.md + echo "pip install cua-agent[all]==${VERSION}" >> release_notes.md + echo '```' >> release_notes.md + elif [ "${{ inputs.package_name }}" = "som" ]; then + echo "## Computer Vision and OCR library for detecting and analyzing UI elements" >> release_notes.md + echo "" >> release_notes.md + echo "This package provides enhanced UI understanding capabilities through computer vision and OCR." >> release_notes.md + elif [ "${{ inputs.package_name }}" = "computer-server" ]; then + echo "## Computer Server for the Computer Universal Automation (CUA) project" >> release_notes.md + echo "" >> release_notes.md + echo "A FastAPI-based server implementation for computer control." >> release_notes.md + echo "" >> release_notes.md + echo "## Dependencies" >> release_notes.md + echo "* cua-computer: ${COMPUTER_VERSION:-latest}" >> release_notes.md + echo "" >> release_notes.md + echo "## Usage" >> release_notes.md + echo '```bash' >> release_notes.md + echo "# Run the server" >> release_notes.md + echo "cua-computer-server" >> release_notes.md + echo '```' >> release_notes.md + elif [ "${{ inputs.package_name }}" = "mcp-server" ]; then + echo "## MCP Server for the Computer-Use Agent (CUA)" >> release_notes.md + echo "" >> release_notes.md + echo "This package provides MCP (Model Context Protocol) integration for CUA agents, allowing them to be used with Claude Desktop, Cursor, and other MCP clients." >> release_notes.md + echo "" >> release_notes.md + echo "## Dependencies" >> release_notes.md + echo "* cua-computer: ${COMPUTER_VERSION:-latest}" >> release_notes.md + echo "* cua-agent: ${AGENT_VERSION:-latest}" >> release_notes.md + echo "" >> release_notes.md + echo "## Usage" >> release_notes.md + echo '```bash' >> release_notes.md + echo "# Run the MCP server directly" >> release_notes.md + echo "cua-mcp-server" >> release_notes.md + echo '```' >> release_notes.md + echo "" >> release_notes.md + echo "## Claude Desktop Integration" >> release_notes.md + echo "Add to your Claude Desktop configuration (~/.config/claude-desktop/claude_desktop_config.json or OS-specific location):" >> release_notes.md + echo '```json' >> release_notes.md + echo '"mcpServers": {' >> release_notes.md + echo ' "cua-agent": {' >> release_notes.md + echo ' "command": "cua-mcp-server",' >> release_notes.md + echo ' "args": [],' >> release_notes.md + echo ' "env": {' >> release_notes.md + echo ' "CUA_AGENT_LOOP": "OMNI",' >> release_notes.md + echo ' "CUA_MODEL_PROVIDER": "ANTHROPIC",' >> release_notes.md + echo ' "CUA_MODEL_NAME": "claude-3-opus-20240229",' >> release_notes.md + echo ' "ANTHROPIC_API_KEY": "your-api-key",' >> release_notes.md + echo ' "PYTHONIOENCODING": "utf-8"' >> release_notes.md + echo ' }' >> release_notes.md + echo ' }' >> release_notes.md + echo '}' >> release_notes.md + echo '```' >> release_notes.md + fi + + # Add installation section if not agent (which has its own installation section) + if [ "${{ inputs.package_name }}" != "agent" ]; then + echo "" >> release_notes.md + echo "## Installation" >> release_notes.md + echo '```bash' >> release_notes.md + echo "pip install ${{ inputs.base_package_name }}==${VERSION}" >> release_notes.md + echo '```' >> release_notes.md + fi + + echo "Release notes created:" + cat release_notes.md + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + if: startsWith(github.ref, 'refs/tags/') + with: + name: "${{ inputs.base_package_name }} v${{ env.VERSION }}" + body_path: release_notes.md + files: ${{ inputs.package_dir }}/${{ env.WHEEL_FILE }} + draft: false + prerelease: false + make_latest: ${{ inputs.package_name == 'lume' }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/libs/python/lumier/.dockerignore b/libs/bash/lumier/.dockerignore similarity index 100% rename from libs/python/lumier/.dockerignore rename to libs/bash/lumier/.dockerignore diff --git a/libs/python/lumier/Dockerfile b/libs/bash/lumier/Dockerfile similarity index 100% rename from libs/python/lumier/Dockerfile rename to libs/bash/lumier/Dockerfile diff --git a/libs/python/lumier/README.md b/libs/bash/lumier/README.md similarity index 100% rename from libs/python/lumier/README.md rename to libs/bash/lumier/README.md diff --git a/libs/python/lumier/src/bin/entry.sh b/libs/bash/lumier/src/bin/entry.sh similarity index 100% rename from libs/python/lumier/src/bin/entry.sh rename to libs/bash/lumier/src/bin/entry.sh diff --git a/libs/python/lumier/src/config/constants.sh b/libs/bash/lumier/src/config/constants.sh similarity index 100% rename from libs/python/lumier/src/config/constants.sh rename to libs/bash/lumier/src/config/constants.sh diff --git a/libs/python/lumier/src/hooks/on-logon.sh b/libs/bash/lumier/src/hooks/on-logon.sh similarity index 100% rename from libs/python/lumier/src/hooks/on-logon.sh rename to libs/bash/lumier/src/hooks/on-logon.sh diff --git a/libs/python/lumier/src/lib/utils.sh b/libs/bash/lumier/src/lib/utils.sh similarity index 100% rename from libs/python/lumier/src/lib/utils.sh rename to libs/bash/lumier/src/lib/utils.sh diff --git a/libs/python/lumier/src/lib/vm.sh b/libs/bash/lumier/src/lib/vm.sh similarity index 100% rename from libs/python/lumier/src/lib/vm.sh rename to libs/bash/lumier/src/lib/vm.sh diff --git a/libs/python/lume/.cursorignore b/libs/swift/lume/.cursorignore similarity index 100% rename from libs/python/lume/.cursorignore rename to libs/swift/lume/.cursorignore diff --git a/libs/python/lume/CONTRIBUTING.md b/libs/swift/lume/CONTRIBUTING.md similarity index 100% rename from libs/python/lume/CONTRIBUTING.md rename to libs/swift/lume/CONTRIBUTING.md diff --git a/libs/python/lume/Package.resolved b/libs/swift/lume/Package.resolved similarity index 100% rename from libs/python/lume/Package.resolved rename to libs/swift/lume/Package.resolved diff --git a/libs/python/lume/Package.swift b/libs/swift/lume/Package.swift similarity index 100% rename from libs/python/lume/Package.swift rename to libs/swift/lume/Package.swift diff --git a/libs/python/lume/README.md b/libs/swift/lume/README.md similarity index 100% rename from libs/python/lume/README.md rename to libs/swift/lume/README.md diff --git a/libs/python/lume/docs/API-Reference.md b/libs/swift/lume/docs/API-Reference.md similarity index 100% rename from libs/python/lume/docs/API-Reference.md rename to libs/swift/lume/docs/API-Reference.md diff --git a/libs/python/lume/docs/Development.md b/libs/swift/lume/docs/Development.md similarity index 100% rename from libs/python/lume/docs/Development.md rename to libs/swift/lume/docs/Development.md diff --git a/libs/python/lume/docs/FAQ.md b/libs/swift/lume/docs/FAQ.md similarity index 100% rename from libs/python/lume/docs/FAQ.md rename to libs/swift/lume/docs/FAQ.md diff --git a/libs/python/lume/img/cli.png b/libs/swift/lume/img/cli.png similarity index 100% rename from libs/python/lume/img/cli.png rename to libs/swift/lume/img/cli.png diff --git a/libs/python/lume/img/logo_black.png b/libs/swift/lume/img/logo_black.png similarity index 100% rename from libs/python/lume/img/logo_black.png rename to libs/swift/lume/img/logo_black.png diff --git a/libs/python/lume/img/logo_white.png b/libs/swift/lume/img/logo_white.png similarity index 100% rename from libs/python/lume/img/logo_white.png rename to libs/swift/lume/img/logo_white.png diff --git a/libs/python/lume/resources/lume.entitlements b/libs/swift/lume/resources/lume.entitlements similarity index 100% rename from libs/python/lume/resources/lume.entitlements rename to libs/swift/lume/resources/lume.entitlements diff --git a/libs/python/lume/scripts/install.sh b/libs/swift/lume/scripts/install.sh similarity index 100% rename from libs/python/lume/scripts/install.sh rename to libs/swift/lume/scripts/install.sh diff --git a/libs/python/lume/src/Commands/Clone.swift b/libs/swift/lume/src/Commands/Clone.swift similarity index 100% rename from libs/python/lume/src/Commands/Clone.swift rename to libs/swift/lume/src/Commands/Clone.swift diff --git a/libs/python/lume/src/Commands/Config.swift b/libs/swift/lume/src/Commands/Config.swift similarity index 100% rename from libs/python/lume/src/Commands/Config.swift rename to libs/swift/lume/src/Commands/Config.swift diff --git a/libs/python/lume/src/Commands/Create.swift b/libs/swift/lume/src/Commands/Create.swift similarity index 100% rename from libs/python/lume/src/Commands/Create.swift rename to libs/swift/lume/src/Commands/Create.swift diff --git a/libs/python/lume/src/Commands/Delete.swift b/libs/swift/lume/src/Commands/Delete.swift similarity index 100% rename from libs/python/lume/src/Commands/Delete.swift rename to libs/swift/lume/src/Commands/Delete.swift diff --git a/libs/python/lume/src/Commands/Get.swift b/libs/swift/lume/src/Commands/Get.swift similarity index 100% rename from libs/python/lume/src/Commands/Get.swift rename to libs/swift/lume/src/Commands/Get.swift diff --git a/libs/python/lume/src/Commands/IPSW.swift b/libs/swift/lume/src/Commands/IPSW.swift similarity index 100% rename from libs/python/lume/src/Commands/IPSW.swift rename to libs/swift/lume/src/Commands/IPSW.swift diff --git a/libs/python/lume/src/Commands/Images.swift b/libs/swift/lume/src/Commands/Images.swift similarity index 100% rename from libs/python/lume/src/Commands/Images.swift rename to libs/swift/lume/src/Commands/Images.swift diff --git a/libs/python/lume/src/Commands/List.swift b/libs/swift/lume/src/Commands/List.swift similarity index 100% rename from libs/python/lume/src/Commands/List.swift rename to libs/swift/lume/src/Commands/List.swift diff --git a/libs/python/lume/src/Commands/Logs.swift b/libs/swift/lume/src/Commands/Logs.swift similarity index 100% rename from libs/python/lume/src/Commands/Logs.swift rename to libs/swift/lume/src/Commands/Logs.swift diff --git a/libs/python/lume/src/Commands/Options/FormatOption.swift b/libs/swift/lume/src/Commands/Options/FormatOption.swift similarity index 100% rename from libs/python/lume/src/Commands/Options/FormatOption.swift rename to libs/swift/lume/src/Commands/Options/FormatOption.swift diff --git a/libs/python/lume/src/Commands/Prune.swift b/libs/swift/lume/src/Commands/Prune.swift similarity index 100% rename from libs/python/lume/src/Commands/Prune.swift rename to libs/swift/lume/src/Commands/Prune.swift diff --git a/libs/python/lume/src/Commands/Pull.swift b/libs/swift/lume/src/Commands/Pull.swift similarity index 100% rename from libs/python/lume/src/Commands/Pull.swift rename to libs/swift/lume/src/Commands/Pull.swift diff --git a/libs/python/lume/src/Commands/Push.swift b/libs/swift/lume/src/Commands/Push.swift similarity index 100% rename from libs/python/lume/src/Commands/Push.swift rename to libs/swift/lume/src/Commands/Push.swift diff --git a/libs/python/lume/src/Commands/Run.swift b/libs/swift/lume/src/Commands/Run.swift similarity index 100% rename from libs/python/lume/src/Commands/Run.swift rename to libs/swift/lume/src/Commands/Run.swift diff --git a/libs/python/lume/src/Commands/Serve.swift b/libs/swift/lume/src/Commands/Serve.swift similarity index 100% rename from libs/python/lume/src/Commands/Serve.swift rename to libs/swift/lume/src/Commands/Serve.swift diff --git a/libs/python/lume/src/Commands/Set.swift b/libs/swift/lume/src/Commands/Set.swift similarity index 100% rename from libs/python/lume/src/Commands/Set.swift rename to libs/swift/lume/src/Commands/Set.swift diff --git a/libs/python/lume/src/Commands/Stop.swift b/libs/swift/lume/src/Commands/Stop.swift similarity index 100% rename from libs/python/lume/src/Commands/Stop.swift rename to libs/swift/lume/src/Commands/Stop.swift diff --git a/libs/python/lume/src/ContainerRegistry/ImageContainerRegistry.swift b/libs/swift/lume/src/ContainerRegistry/ImageContainerRegistry.swift similarity index 100% rename from libs/python/lume/src/ContainerRegistry/ImageContainerRegistry.swift rename to libs/swift/lume/src/ContainerRegistry/ImageContainerRegistry.swift diff --git a/libs/python/lume/src/ContainerRegistry/ImageList.swift b/libs/swift/lume/src/ContainerRegistry/ImageList.swift similarity index 100% rename from libs/python/lume/src/ContainerRegistry/ImageList.swift rename to libs/swift/lume/src/ContainerRegistry/ImageList.swift diff --git a/libs/python/lume/src/ContainerRegistry/ImagesPrinter.swift b/libs/swift/lume/src/ContainerRegistry/ImagesPrinter.swift similarity index 100% rename from libs/python/lume/src/ContainerRegistry/ImagesPrinter.swift rename to libs/swift/lume/src/ContainerRegistry/ImagesPrinter.swift diff --git a/libs/python/lume/src/Errors/Errors.swift b/libs/swift/lume/src/Errors/Errors.swift similarity index 100% rename from libs/python/lume/src/Errors/Errors.swift rename to libs/swift/lume/src/Errors/Errors.swift diff --git a/libs/python/lume/src/FileSystem/Home.swift b/libs/swift/lume/src/FileSystem/Home.swift similarity index 100% rename from libs/python/lume/src/FileSystem/Home.swift rename to libs/swift/lume/src/FileSystem/Home.swift diff --git a/libs/python/lume/src/FileSystem/Settings.swift b/libs/swift/lume/src/FileSystem/Settings.swift similarity index 100% rename from libs/python/lume/src/FileSystem/Settings.swift rename to libs/swift/lume/src/FileSystem/Settings.swift diff --git a/libs/python/lume/src/FileSystem/VMConfig.swift b/libs/swift/lume/src/FileSystem/VMConfig.swift similarity index 100% rename from libs/python/lume/src/FileSystem/VMConfig.swift rename to libs/swift/lume/src/FileSystem/VMConfig.swift diff --git a/libs/python/lume/src/FileSystem/VMDirectory.swift b/libs/swift/lume/src/FileSystem/VMDirectory.swift similarity index 100% rename from libs/python/lume/src/FileSystem/VMDirectory.swift rename to libs/swift/lume/src/FileSystem/VMDirectory.swift diff --git a/libs/python/lume/src/FileSystem/VMLocation.swift b/libs/swift/lume/src/FileSystem/VMLocation.swift similarity index 100% rename from libs/python/lume/src/FileSystem/VMLocation.swift rename to libs/swift/lume/src/FileSystem/VMLocation.swift diff --git a/libs/python/lume/src/LumeController.swift b/libs/swift/lume/src/LumeController.swift similarity index 100% rename from libs/python/lume/src/LumeController.swift rename to libs/swift/lume/src/LumeController.swift diff --git a/libs/python/lume/src/Main.swift b/libs/swift/lume/src/Main.swift similarity index 100% rename from libs/python/lume/src/Main.swift rename to libs/swift/lume/src/Main.swift diff --git a/libs/python/lume/src/Server/HTTP.swift b/libs/swift/lume/src/Server/HTTP.swift similarity index 100% rename from libs/python/lume/src/Server/HTTP.swift rename to libs/swift/lume/src/Server/HTTP.swift diff --git a/libs/python/lume/src/Server/Handlers.swift b/libs/swift/lume/src/Server/Handlers.swift similarity index 100% rename from libs/python/lume/src/Server/Handlers.swift rename to libs/swift/lume/src/Server/Handlers.swift diff --git a/libs/python/lume/src/Server/Requests.swift b/libs/swift/lume/src/Server/Requests.swift similarity index 100% rename from libs/python/lume/src/Server/Requests.swift rename to libs/swift/lume/src/Server/Requests.swift diff --git a/libs/python/lume/src/Server/Responses.swift b/libs/swift/lume/src/Server/Responses.swift similarity index 100% rename from libs/python/lume/src/Server/Responses.swift rename to libs/swift/lume/src/Server/Responses.swift diff --git a/libs/python/lume/src/Server/Server.swift b/libs/swift/lume/src/Server/Server.swift similarity index 100% rename from libs/python/lume/src/Server/Server.swift rename to libs/swift/lume/src/Server/Server.swift diff --git a/libs/python/lume/src/Utils/CommandRegistry.swift b/libs/swift/lume/src/Utils/CommandRegistry.swift similarity index 100% rename from libs/python/lume/src/Utils/CommandRegistry.swift rename to libs/swift/lume/src/Utils/CommandRegistry.swift diff --git a/libs/python/lume/src/Utils/CommandUtils.swift b/libs/swift/lume/src/Utils/CommandUtils.swift similarity index 100% rename from libs/python/lume/src/Utils/CommandUtils.swift rename to libs/swift/lume/src/Utils/CommandUtils.swift diff --git a/libs/python/lume/src/Utils/Logger.swift b/libs/swift/lume/src/Utils/Logger.swift similarity index 100% rename from libs/python/lume/src/Utils/Logger.swift rename to libs/swift/lume/src/Utils/Logger.swift diff --git a/libs/python/lume/src/Utils/NetworkUtils.swift b/libs/swift/lume/src/Utils/NetworkUtils.swift similarity index 100% rename from libs/python/lume/src/Utils/NetworkUtils.swift rename to libs/swift/lume/src/Utils/NetworkUtils.swift diff --git a/libs/python/lume/src/Utils/Path.swift b/libs/swift/lume/src/Utils/Path.swift similarity index 100% rename from libs/python/lume/src/Utils/Path.swift rename to libs/swift/lume/src/Utils/Path.swift diff --git a/libs/python/lume/src/Utils/ProcessRunner.swift b/libs/swift/lume/src/Utils/ProcessRunner.swift similarity index 100% rename from libs/python/lume/src/Utils/ProcessRunner.swift rename to libs/swift/lume/src/Utils/ProcessRunner.swift diff --git a/libs/python/lume/src/Utils/ProgressLogger.swift b/libs/swift/lume/src/Utils/ProgressLogger.swift similarity index 100% rename from libs/python/lume/src/Utils/ProgressLogger.swift rename to libs/swift/lume/src/Utils/ProgressLogger.swift diff --git a/libs/python/lume/src/Utils/String.swift b/libs/swift/lume/src/Utils/String.swift similarity index 100% rename from libs/python/lume/src/Utils/String.swift rename to libs/swift/lume/src/Utils/String.swift diff --git a/libs/python/lume/src/Utils/Utils.swift b/libs/swift/lume/src/Utils/Utils.swift similarity index 100% rename from libs/python/lume/src/Utils/Utils.swift rename to libs/swift/lume/src/Utils/Utils.swift diff --git a/libs/python/lume/src/VM/DarwinVM.swift b/libs/swift/lume/src/VM/DarwinVM.swift similarity index 100% rename from libs/python/lume/src/VM/DarwinVM.swift rename to libs/swift/lume/src/VM/DarwinVM.swift diff --git a/libs/python/lume/src/VM/LinuxVM.swift b/libs/swift/lume/src/VM/LinuxVM.swift similarity index 100% rename from libs/python/lume/src/VM/LinuxVM.swift rename to libs/swift/lume/src/VM/LinuxVM.swift diff --git a/libs/python/lume/src/VM/VM.swift b/libs/swift/lume/src/VM/VM.swift similarity index 100% rename from libs/python/lume/src/VM/VM.swift rename to libs/swift/lume/src/VM/VM.swift diff --git a/libs/python/lume/src/VM/VMDetails.swift b/libs/swift/lume/src/VM/VMDetails.swift similarity index 100% rename from libs/python/lume/src/VM/VMDetails.swift rename to libs/swift/lume/src/VM/VMDetails.swift diff --git a/libs/python/lume/src/VM/VMDetailsPrinter.swift b/libs/swift/lume/src/VM/VMDetailsPrinter.swift similarity index 100% rename from libs/python/lume/src/VM/VMDetailsPrinter.swift rename to libs/swift/lume/src/VM/VMDetailsPrinter.swift diff --git a/libs/python/lume/src/VM/VMDisplayResolution.swift b/libs/swift/lume/src/VM/VMDisplayResolution.swift similarity index 100% rename from libs/python/lume/src/VM/VMDisplayResolution.swift rename to libs/swift/lume/src/VM/VMDisplayResolution.swift diff --git a/libs/python/lume/src/VM/VMFactory.swift b/libs/swift/lume/src/VM/VMFactory.swift similarity index 100% rename from libs/python/lume/src/VM/VMFactory.swift rename to libs/swift/lume/src/VM/VMFactory.swift diff --git a/libs/python/lume/src/VNC/PassphraseGenerator.swift b/libs/swift/lume/src/VNC/PassphraseGenerator.swift similarity index 100% rename from libs/python/lume/src/VNC/PassphraseGenerator.swift rename to libs/swift/lume/src/VNC/PassphraseGenerator.swift diff --git a/libs/python/lume/src/VNC/VNCService.swift b/libs/swift/lume/src/VNC/VNCService.swift similarity index 100% rename from libs/python/lume/src/VNC/VNCService.swift rename to libs/swift/lume/src/VNC/VNCService.swift diff --git a/libs/python/lume/src/Virtualization/DHCPLeaseParser.swift b/libs/swift/lume/src/Virtualization/DHCPLeaseParser.swift similarity index 100% rename from libs/python/lume/src/Virtualization/DHCPLeaseParser.swift rename to libs/swift/lume/src/Virtualization/DHCPLeaseParser.swift diff --git a/libs/python/lume/src/Virtualization/DarwinImageLoader.swift b/libs/swift/lume/src/Virtualization/DarwinImageLoader.swift similarity index 100% rename from libs/python/lume/src/Virtualization/DarwinImageLoader.swift rename to libs/swift/lume/src/Virtualization/DarwinImageLoader.swift diff --git a/libs/python/lume/src/Virtualization/ImageLoaderFactory.swift b/libs/swift/lume/src/Virtualization/ImageLoaderFactory.swift similarity index 100% rename from libs/python/lume/src/Virtualization/ImageLoaderFactory.swift rename to libs/swift/lume/src/Virtualization/ImageLoaderFactory.swift diff --git a/libs/python/lume/src/Virtualization/VMVirtualizationService.swift b/libs/swift/lume/src/Virtualization/VMVirtualizationService.swift similarity index 100% rename from libs/python/lume/src/Virtualization/VMVirtualizationService.swift rename to libs/swift/lume/src/Virtualization/VMVirtualizationService.swift diff --git a/libs/python/lume/tests/Mocks/MockVM.swift b/libs/swift/lume/tests/Mocks/MockVM.swift similarity index 100% rename from libs/python/lume/tests/Mocks/MockVM.swift rename to libs/swift/lume/tests/Mocks/MockVM.swift diff --git a/libs/python/lume/tests/Mocks/MockVMVirtualizationService.swift b/libs/swift/lume/tests/Mocks/MockVMVirtualizationService.swift similarity index 100% rename from libs/python/lume/tests/Mocks/MockVMVirtualizationService.swift rename to libs/swift/lume/tests/Mocks/MockVMVirtualizationService.swift diff --git a/libs/python/lume/tests/Mocks/MockVNCService.swift b/libs/swift/lume/tests/Mocks/MockVNCService.swift similarity index 100% rename from libs/python/lume/tests/Mocks/MockVNCService.swift rename to libs/swift/lume/tests/Mocks/MockVNCService.swift diff --git a/libs/python/lume/tests/VM/VMDetailsPrinterTests.swift b/libs/swift/lume/tests/VM/VMDetailsPrinterTests.swift similarity index 100% rename from libs/python/lume/tests/VM/VMDetailsPrinterTests.swift rename to libs/swift/lume/tests/VM/VMDetailsPrinterTests.swift diff --git a/libs/python/lume/tests/VMTests.swift b/libs/swift/lume/tests/VMTests.swift similarity index 100% rename from libs/python/lume/tests/VMTests.swift rename to libs/swift/lume/tests/VMTests.swift diff --git a/libs/python/lume/tests/VMVirtualizationServiceTests.swift b/libs/swift/lume/tests/VMVirtualizationServiceTests.swift similarity index 100% rename from libs/python/lume/tests/VMVirtualizationServiceTests.swift rename to libs/swift/lume/tests/VMVirtualizationServiceTests.swift diff --git a/libs/python/lume/tests/VNCServiceTests.swift b/libs/swift/lume/tests/VNCServiceTests.swift similarity index 100% rename from libs/python/lume/tests/VNCServiceTests.swift rename to libs/swift/lume/tests/VNCServiceTests.swift From 4625b65babe6a1fd29c71c4ff04e93552d0b203d Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 25 Jun 2025 16:19:25 -0700 Subject: [PATCH 109/141] Create (untested) npm deploy workflows --- .github/workflows/publish-computer-ts.yml | 32 ++++++++++++++++ .github/workflows/publish-core-ts.yml | 32 ++++++++++++++++ .../computer/.github/_workflows/release.yml | 26 ------------- .../computer/.github/_workflows/unit-test.yml | 38 ------------------- .../core/.github/_workflows/release.yml | 26 ------------- .../core/.github/_workflows/unit-test.yml | 38 ------------------- libs/typescript/core/.vscode/settings.json | 3 -- 7 files changed, 64 insertions(+), 131 deletions(-) create mode 100644 .github/workflows/publish-computer-ts.yml create mode 100644 .github/workflows/publish-core-ts.yml delete mode 100644 libs/typescript/computer/.github/_workflows/release.yml delete mode 100644 libs/typescript/computer/.github/_workflows/unit-test.yml delete mode 100644 libs/typescript/core/.github/_workflows/release.yml delete mode 100644 libs/typescript/core/.github/_workflows/unit-test.yml delete mode 100644 libs/typescript/core/.vscode/settings.json diff --git a/.github/workflows/publish-computer-ts.yml b/.github/workflows/publish-computer-ts.yml new file mode 100644 index 00000000..6d9f72d9 --- /dev/null +++ b/.github/workflows/publish-computer-ts.yml @@ -0,0 +1,32 @@ +name: Publish @cua/computer to npm + +on: + push: + tags: + - 'computer-v*' + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js LTS + uses: actions/setup-node@v4 + with: + node-version: lts/* + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + working-directory: ./libs/typescript/computer + run: npm ci + + - name: Build package + working-directory: ./libs/typescript/computer + run: npm run build --if-present + + - name: Publish to npm + working-directory: ./libs/typescript/computer + run: npm publish --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/publish-core-ts.yml b/.github/workflows/publish-core-ts.yml new file mode 100644 index 00000000..61e26283 --- /dev/null +++ b/.github/workflows/publish-core-ts.yml @@ -0,0 +1,32 @@ +name: Publish @cua/core to npm + +on: + push: + tags: + - 'core-v*' + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js LTS + uses: actions/setup-node@v4 + with: + node-version: lts/* + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + working-directory: ./libs/typescript/core + run: npm ci + + - name: Build package + working-directory: ./libs/typescript/core + run: npm run build --if-present + + - name: Publish to npm + working-directory: ./libs/typescript/core + run: npm publish --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/libs/typescript/computer/.github/_workflows/release.yml b/libs/typescript/computer/.github/_workflows/release.yml deleted file mode 100644 index e5b7a4c3..00000000 --- a/libs/typescript/computer/.github/_workflows/release.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Release - -permissions: - contents: write - -on: - push: - tags: - - 'v*' - -jobs: - release: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set node - uses: actions/setup-node@v4 - with: - node-version: lts/* - - - run: npx changelogithub - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/libs/typescript/computer/.github/_workflows/unit-test.yml b/libs/typescript/computer/.github/_workflows/unit-test.yml deleted file mode 100644 index dc3418c5..00000000 --- a/libs/typescript/computer/.github/_workflows/unit-test.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Unit Test - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install pnpm - uses: pnpm/action-setup@v4.1.0 - - - name: Set node LTS - uses: actions/setup-node@v4 - with: - node-version: lts/* - cache: pnpm - - - name: Install - run: pnpm install - - - name: Build - run: pnpm run build - - - name: Lint - run: pnpm run lint - - - name: Typecheck - run: pnpm run typecheck - - - name: Test - run: pnpm run test diff --git a/libs/typescript/core/.github/_workflows/release.yml b/libs/typescript/core/.github/_workflows/release.yml deleted file mode 100644 index e5b7a4c3..00000000 --- a/libs/typescript/core/.github/_workflows/release.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Release - -permissions: - contents: write - -on: - push: - tags: - - 'v*' - -jobs: - release: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Set node - uses: actions/setup-node@v4 - with: - node-version: lts/* - - - run: npx changelogithub - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/libs/typescript/core/.github/_workflows/unit-test.yml b/libs/typescript/core/.github/_workflows/unit-test.yml deleted file mode 100644 index dc3418c5..00000000 --- a/libs/typescript/core/.github/_workflows/unit-test.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Unit Test - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - test: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install pnpm - uses: pnpm/action-setup@v4.1.0 - - - name: Set node LTS - uses: actions/setup-node@v4 - with: - node-version: lts/* - cache: pnpm - - - name: Install - run: pnpm install - - - name: Build - run: pnpm run build - - - name: Lint - run: pnpm run lint - - - name: Typecheck - run: pnpm run typecheck - - - name: Test - run: pnpm run test diff --git a/libs/typescript/core/.vscode/settings.json b/libs/typescript/core/.vscode/settings.json deleted file mode 100644 index ad92582b..00000000 --- a/libs/typescript/core/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "editor.formatOnSave": true -} From e61b92e0b92b694705fcd0c12d10b89429c8ea83 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Fri, 27 Jun 2025 10:17:35 -0400 Subject: [PATCH 110/141] Added backwards compat for winsandbox --- .../computer/providers/winsandbox/provider.py | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/libs/computer/computer/providers/winsandbox/provider.py b/libs/computer/computer/providers/winsandbox/provider.py index a0b61055..6196b96f 100644 --- a/libs/computer/computer/providers/winsandbox/provider.py +++ b/libs/computer/computer/providers/winsandbox/provider.py @@ -262,11 +262,28 @@ class WinSandboxProvider(BaseVMProvider): self.logger.info(f"Shared directories: {len(folder_mappers)}") # Create the sandbox without logon script - sandbox = winsandbox.new_sandbox( - memory_mb=str(memory_mb), - networking=networking, - folder_mappers=folder_mappers - ) + try: + # Try with memory_mb parameter (newer pywinsandbox version) + sandbox = winsandbox.new_sandbox( + memory_mb=str(memory_mb), + networking=networking, + folder_mappers=folder_mappers + ) + except TypeError as e: + if "memory_mb" in str(e): + # Fallback for older pywinsandbox version that doesn't support memory_mb + self.logger.warning( + f"Your pywinsandbox version doesn't support memory_mb parameter. " + f"Using default memory settings. To use custom memory settings, " + f"please update pywinsandbox: pip install -U git+https://github.com/karkason/pywinsandbox.git" + ) + sandbox = winsandbox.new_sandbox( + networking=networking, + folder_mappers=folder_mappers + ) + else: + # Re-raise if it's a different TypeError + raise # Store the sandbox self._active_sandboxes[name] = sandbox From 6a6ff87cdbc504f5c812c82d43fd64e593fe11fb Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Fri, 27 Jun 2025 10:38:45 -0400 Subject: [PATCH 111/141] Added support for large file reading (#306) --- .../computer_server/handlers/base.py | 21 ++++++-- .../computer_server/handlers/generic.py | 24 ++++++++-- libs/computer-server/computer_server/main.py | 1 + libs/computer/computer/interface/base.py | 15 +++++- libs/computer/computer/interface/linux.py | 48 ++++++++++++++++++- libs/computer/computer/interface/macos.py | 48 ++++++++++++++++++- libs/computer/computer/interface/windows.py | 48 ++++++++++++++++++- 7 files changed, 189 insertions(+), 16 deletions(-) diff --git a/libs/computer-server/computer_server/handlers/base.py b/libs/computer-server/computer_server/handlers/base.py index 82a8204e..012a296c 100644 --- a/libs/computer-server/computer_server/handlers/base.py +++ b/libs/computer-server/computer_server/handlers/base.py @@ -44,11 +44,6 @@ class BaseFileHandler(ABC): """Write text content to a file.""" pass - @abstractmethod - async def read_bytes(self, path: str) -> Dict[str, Any]: - """Read the binary contents of a file. Sent over the websocket as a base64 string.""" - pass - @abstractmethod async def write_bytes(self, path: str, content_b64: str) -> Dict[str, Any]: """Write binary content to a file. Sent over the websocket as a base64 string.""" @@ -69,6 +64,22 @@ class BaseFileHandler(ABC): """Delete a directory.""" pass + @abstractmethod + async def read_bytes(self, path: str, offset: int = 0, length: Optional[int] = None) -> Dict[str, Any]: + """Read the binary contents of a file. Sent over the websocket as a base64 string. + + Args: + path: Path to the file + offset: Byte offset to start reading from (default: 0) + length: Number of bytes to read (default: None for entire file) + """ + pass + + @abstractmethod + async def get_file_size(self, path: str) -> Dict[str, Any]: + """Get the size of a file in bytes.""" + pass + class BaseAutomationHandler(ABC): """Abstract base class for OS-specific automation handlers. diff --git a/libs/computer-server/computer_server/handlers/generic.py b/libs/computer-server/computer_server/handlers/generic.py index 784900ef..cdf9df7d 100644 --- a/libs/computer-server/computer_server/handlers/generic.py +++ b/libs/computer-server/computer_server/handlers/generic.py @@ -7,7 +7,7 @@ Includes: """ from pathlib import Path -from typing import Dict, Any +from typing import Dict, Any, Optional from .base import BaseFileHandler import base64 @@ -54,9 +54,27 @@ class GenericFileHandler(BaseFileHandler): except Exception as e: return {"success": False, "error": str(e)} - async def read_bytes(self, path: str) -> Dict[str, Any]: + async def read_bytes(self, path: str, offset: int = 0, length: Optional[int] = None) -> Dict[str, Any]: try: - return {"success": True, "content_b64": base64.b64encode(resolve_path(path).read_bytes()).decode('utf-8')} + file_path = resolve_path(path) + with open(file_path, 'rb') as f: + if offset > 0: + f.seek(offset) + + if length is not None: + content = f.read(length) + else: + content = f.read() + + return {"success": True, "content_b64": base64.b64encode(content).decode('utf-8')} + except Exception as e: + return {"success": False, "error": str(e)} + + async def get_file_size(self, path: str) -> Dict[str, Any]: + try: + file_path = resolve_path(path) + size = file_path.stat().st_size + return {"success": True, "size": size} except Exception as e: return {"success": False, "error": str(e)} diff --git a/libs/computer-server/computer_server/main.py b/libs/computer-server/computer_server/main.py index 7a0dd515..de422887 100644 --- a/libs/computer-server/computer_server/main.py +++ b/libs/computer-server/computer_server/main.py @@ -172,6 +172,7 @@ async def websocket_endpoint(websocket: WebSocket): "write_text": manager.file_handler.write_text, "read_bytes": manager.file_handler.read_bytes, "write_bytes": manager.file_handler.write_bytes, + "get_file_size": manager.file_handler.get_file_size, "delete_file": manager.file_handler.delete_file, "create_dir": manager.file_handler.create_dir, "delete_dir": manager.file_handler.delete_dir, diff --git a/libs/computer/computer/interface/base.py b/libs/computer/computer/interface/base.py index a069e8c8..183ebd2d 100644 --- a/libs/computer/computer/interface/base.py +++ b/libs/computer/computer/interface/base.py @@ -208,8 +208,14 @@ class BaseComputerInterface(ABC): pass @abstractmethod - async def read_bytes(self, path: str) -> bytes: - """Read file binary contents.""" + async def read_bytes(self, path: str, offset: int = 0, length: Optional[int] = None) -> bytes: + """Read file binary contents with optional seeking support. + + Args: + path: Path to the file + offset: Byte offset to start reading from (default: 0) + length: Number of bytes to read (default: None for entire file) + """ pass @abstractmethod @@ -232,6 +238,11 @@ class BaseComputerInterface(ABC): """Delete directory.""" pass + @abstractmethod + async def get_file_size(self, path: str) -> int: + """Get the size of a file in bytes.""" + pass + @abstractmethod async def run_command(self, command: str) -> CommandResult: """Run shell command and return structured result. diff --git a/libs/computer/computer/interface/linux.py b/libs/computer/computer/interface/linux.py index b87118a2..11840765 100644 --- a/libs/computer/computer/interface/linux.py +++ b/libs/computer/computer/interface/linux.py @@ -588,13 +588,57 @@ class LinuxComputerInterface(BaseComputerInterface): if not result.get("success", False): raise RuntimeError(result.get("error", "Failed to write file")) - async def read_bytes(self, path: str) -> bytes: - result = await self._send_command("read_bytes", {"path": path}) + async def read_bytes(self, path: str, offset: int = 0, length: Optional[int] = None) -> bytes: + # For large files, use chunked reading + if length is None: + # Get file size first to determine if we need chunking + file_size = await self.get_file_size(path) + # If file is larger than 5MB, read in chunks + if file_size > 5 * 1024 * 1024: # 5MB threshold + return await self._read_bytes_chunked(path, offset, file_size - offset if offset > 0 else file_size) + + result = await self._send_command("read_bytes", { + "path": path, + "offset": offset, + "length": length + }) if not result.get("success", False): raise RuntimeError(result.get("error", "Failed to read file")) content_b64 = result.get("content_b64", "") return decode_base64_image(content_b64) + async def get_file_size(self, path: str) -> int: + result = await self._send_command("get_file_size", {"path": path}) + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to get file size")) + return result.get("size", 0) + + async def _read_bytes_chunked(self, path: str, offset: int, total_length: int, chunk_size: int = 1024 * 1024) -> bytes: + """Read large files in chunks to avoid memory issues.""" + chunks = [] + current_offset = offset + remaining = total_length + + while remaining > 0: + read_size = min(chunk_size, remaining) + result = await self._send_command("read_bytes", { + "path": path, + "offset": current_offset, + "length": read_size + }) + + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to read file chunk")) + + content_b64 = result.get("content_b64", "") + chunk_data = decode_base64_image(content_b64) + chunks.append(chunk_data) + + current_offset += read_size + remaining -= read_size + + return b''.join(chunks) + async def write_bytes(self, path: str, content: bytes) -> None: result = await self._send_command("write_bytes", {"path": path, "content_b64": encode_base64_image(content)}) if not result.get("success", False): diff --git a/libs/computer/computer/interface/macos.py b/libs/computer/computer/interface/macos.py index cd31e74e..4419cabf 100644 --- a/libs/computer/computer/interface/macos.py +++ b/libs/computer/computer/interface/macos.py @@ -595,13 +595,57 @@ class MacOSComputerInterface(BaseComputerInterface): if not result.get("success", False): raise RuntimeError(result.get("error", "Failed to write file")) - async def read_bytes(self, path: str) -> bytes: - result = await self._send_command("read_bytes", {"path": path}) + async def read_bytes(self, path: str, offset: int = 0, length: Optional[int] = None) -> bytes: + # For large files, use chunked reading + if length is None: + # Get file size first to determine if we need chunking + file_size = await self.get_file_size(path) + # If file is larger than 5MB, read in chunks + if file_size > 5 * 1024 * 1024: # 5MB threshold + return await self._read_bytes_chunked(path, offset, file_size - offset if offset > 0 else file_size) + + result = await self._send_command("read_bytes", { + "path": path, + "offset": offset, + "length": length + }) if not result.get("success", False): raise RuntimeError(result.get("error", "Failed to read file")) content_b64 = result.get("content_b64", "") return decode_base64_image(content_b64) + async def get_file_size(self, path: str) -> int: + result = await self._send_command("get_file_size", {"path": path}) + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to get file size")) + return result.get("size", 0) + + async def _read_bytes_chunked(self, path: str, offset: int, total_length: int, chunk_size: int = 1024 * 1024) -> bytes: + """Read large files in chunks to avoid memory issues.""" + chunks = [] + current_offset = offset + remaining = total_length + + while remaining > 0: + read_size = min(chunk_size, remaining) + result = await self._send_command("read_bytes", { + "path": path, + "offset": current_offset, + "length": read_size + }) + + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to read file chunk")) + + content_b64 = result.get("content_b64", "") + chunk_data = decode_base64_image(content_b64) + chunks.append(chunk_data) + + current_offset += read_size + remaining -= read_size + + return b''.join(chunks) + async def write_bytes(self, path: str, content: bytes) -> None: result = await self._send_command("write_bytes", {"path": path, "content_b64": encode_base64_image(content)}) if not result.get("success", False): diff --git a/libs/computer/computer/interface/windows.py b/libs/computer/computer/interface/windows.py index 7f471e7a..b70feb0f 100644 --- a/libs/computer/computer/interface/windows.py +++ b/libs/computer/computer/interface/windows.py @@ -587,13 +587,57 @@ class WindowsComputerInterface(BaseComputerInterface): if not result.get("success", False): raise RuntimeError(result.get("error", "Failed to write file")) - async def read_bytes(self, path: str) -> bytes: - result = await self._send_command("read_bytes", {"path": path}) + async def read_bytes(self, path: str, offset: int = 0, length: Optional[int] = None) -> bytes: + # For large files, use chunked reading + if length is None: + # Get file size first to determine if we need chunking + file_size = await self.get_file_size(path) + # If file is larger than 5MB, read in chunks + if file_size > 5 * 1024 * 1024: # 5MB threshold + return await self._read_bytes_chunked(path, offset, file_size - offset if offset > 0 else file_size) + + result = await self._send_command("read_bytes", { + "path": path, + "offset": offset, + "length": length + }) if not result.get("success", False): raise RuntimeError(result.get("error", "Failed to read file")) content_b64 = result.get("content_b64", "") return decode_base64_image(content_b64) + async def get_file_size(self, path: str) -> int: + result = await self._send_command("get_file_size", {"path": path}) + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to get file size")) + return result.get("size", 0) + + async def _read_bytes_chunked(self, path: str, offset: int, total_length: int, chunk_size: int = 1024 * 1024) -> bytes: + """Read large files in chunks to avoid memory issues.""" + chunks = [] + current_offset = offset + remaining = total_length + + while remaining > 0: + read_size = min(chunk_size, remaining) + result = await self._send_command("read_bytes", { + "path": path, + "offset": current_offset, + "length": read_size + }) + + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to read file chunk")) + + content_b64 = result.get("content_b64", "") + chunk_data = decode_base64_image(content_b64) + chunks.append(chunk_data) + + current_offset += read_size + remaining -= read_size + + return b''.join(chunks) + async def write_bytes(self, path: str, content: bytes) -> None: result = await self._send_command("write_bytes", {"path": path, "content_b64": encode_base64_image(content)}) if not result.get("success", False): From 8bfa8fbc92c4473d27726c30442a2008e1f93497 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Mon, 30 Jun 2025 10:13:49 -0700 Subject: [PATCH 112/141] move lume & lumier to single directories --- libs/{swift => }/lume/.cursorignore | 0 libs/{swift => }/lume/CONTRIBUTING.md | 0 libs/{swift => }/lume/Package.resolved | 0 libs/{swift => }/lume/Package.swift | 0 libs/{swift => }/lume/README.md | 0 libs/{swift => }/lume/docs/API-Reference.md | 0 libs/{swift => }/lume/docs/Development.md | 0 libs/{swift => }/lume/docs/FAQ.md | 0 libs/{swift => }/lume/img/cli.png | Bin libs/{swift => }/lume/img/logo_black.png | Bin libs/{swift => }/lume/img/logo_white.png | Bin .../lume/resources/lume.entitlements | 0 libs/lume/scripts/build/build-debug.sh | 4 + .../scripts/build/build-release-notarized.sh | 211 ++++++++++++++++++ libs/lume/scripts/build/build-release.sh | 21 ++ libs/{swift => }/lume/scripts/install.sh | 0 .../{swift => }/lume/src/Commands/Clone.swift | 0 .../lume/src/Commands/Config.swift | 0 .../lume/src/Commands/Create.swift | 0 .../lume/src/Commands/Delete.swift | 0 libs/{swift => }/lume/src/Commands/Get.swift | 0 libs/{swift => }/lume/src/Commands/IPSW.swift | 0 .../lume/src/Commands/Images.swift | 0 libs/{swift => }/lume/src/Commands/List.swift | 0 libs/{swift => }/lume/src/Commands/Logs.swift | 0 .../src/Commands/Options/FormatOption.swift | 0 .../{swift => }/lume/src/Commands/Prune.swift | 0 libs/{swift => }/lume/src/Commands/Pull.swift | 0 libs/{swift => }/lume/src/Commands/Push.swift | 0 libs/{swift => }/lume/src/Commands/Run.swift | 0 .../{swift => }/lume/src/Commands/Serve.swift | 0 libs/{swift => }/lume/src/Commands/Set.swift | 0 libs/{swift => }/lume/src/Commands/Stop.swift | 0 .../ImageContainerRegistry.swift | 0 .../src/ContainerRegistry/ImageList.swift | 0 .../src/ContainerRegistry/ImagesPrinter.swift | 0 libs/{swift => }/lume/src/Errors/Errors.swift | 0 .../lume/src/FileSystem/Home.swift | 0 .../lume/src/FileSystem/Settings.swift | 0 .../lume/src/FileSystem/VMConfig.swift | 0 .../lume/src/FileSystem/VMDirectory.swift | 0 .../lume/src/FileSystem/VMLocation.swift | 0 .../{swift => }/lume/src/LumeController.swift | 0 libs/{swift => }/lume/src/Main.swift | 0 libs/{swift => }/lume/src/Server/HTTP.swift | 0 .../lume/src/Server/Handlers.swift | 0 .../lume/src/Server/Requests.swift | 0 .../lume/src/Server/Responses.swift | 0 libs/{swift => }/lume/src/Server/Server.swift | 0 .../lume/src/Utils/CommandRegistry.swift | 0 .../lume/src/Utils/CommandUtils.swift | 0 libs/{swift => }/lume/src/Utils/Logger.swift | 0 .../lume/src/Utils/NetworkUtils.swift | 0 libs/{swift => }/lume/src/Utils/Path.swift | 0 .../lume/src/Utils/ProcessRunner.swift | 0 .../lume/src/Utils/ProgressLogger.swift | 0 libs/{swift => }/lume/src/Utils/String.swift | 0 libs/{swift => }/lume/src/Utils/Utils.swift | 0 libs/{swift => }/lume/src/VM/DarwinVM.swift | 0 libs/{swift => }/lume/src/VM/LinuxVM.swift | 0 libs/{swift => }/lume/src/VM/VM.swift | 0 libs/{swift => }/lume/src/VM/VMDetails.swift | 0 .../lume/src/VM/VMDetailsPrinter.swift | 0 .../lume/src/VM/VMDisplayResolution.swift | 0 libs/{swift => }/lume/src/VM/VMFactory.swift | 0 .../lume/src/VNC/PassphraseGenerator.swift | 0 .../{swift => }/lume/src/VNC/VNCService.swift | 0 .../src/Virtualization/DHCPLeaseParser.swift | 0 .../Virtualization/DarwinImageLoader.swift | 0 .../Virtualization/ImageLoaderFactory.swift | 0 .../VMVirtualizationService.swift | 0 .../{swift => }/lume/tests/Mocks/MockVM.swift | 0 .../Mocks/MockVMVirtualizationService.swift | 0 .../lume/tests/Mocks/MockVNCService.swift | 0 .../lume/tests/VM/VMDetailsPrinterTests.swift | 0 libs/{swift => }/lume/tests/VMTests.swift | 0 .../tests/VMVirtualizationServiceTests.swift | 0 .../lume/tests/VNCServiceTests.swift | 0 libs/{bash => }/lumier/.dockerignore | 0 libs/{bash => }/lumier/Dockerfile | 0 libs/{bash => }/lumier/README.md | 0 libs/{bash => }/lumier/src/bin/entry.sh | 0 .../{bash => }/lumier/src/config/constants.sh | 0 libs/{bash => }/lumier/src/hooks/on-logon.sh | 0 libs/{bash => }/lumier/src/lib/utils.sh | 0 libs/{bash => }/lumier/src/lib/vm.sh | 0 86 files changed, 236 insertions(+) rename libs/{swift => }/lume/.cursorignore (100%) rename libs/{swift => }/lume/CONTRIBUTING.md (100%) rename libs/{swift => }/lume/Package.resolved (100%) rename libs/{swift => }/lume/Package.swift (100%) rename libs/{swift => }/lume/README.md (100%) rename libs/{swift => }/lume/docs/API-Reference.md (100%) rename libs/{swift => }/lume/docs/Development.md (100%) rename libs/{swift => }/lume/docs/FAQ.md (100%) rename libs/{swift => }/lume/img/cli.png (100%) rename libs/{swift => }/lume/img/logo_black.png (100%) rename libs/{swift => }/lume/img/logo_white.png (100%) rename libs/{swift => }/lume/resources/lume.entitlements (100%) create mode 100755 libs/lume/scripts/build/build-debug.sh create mode 100755 libs/lume/scripts/build/build-release-notarized.sh create mode 100755 libs/lume/scripts/build/build-release.sh rename libs/{swift => }/lume/scripts/install.sh (100%) rename libs/{swift => }/lume/src/Commands/Clone.swift (100%) rename libs/{swift => }/lume/src/Commands/Config.swift (100%) rename libs/{swift => }/lume/src/Commands/Create.swift (100%) rename libs/{swift => }/lume/src/Commands/Delete.swift (100%) rename libs/{swift => }/lume/src/Commands/Get.swift (100%) rename libs/{swift => }/lume/src/Commands/IPSW.swift (100%) rename libs/{swift => }/lume/src/Commands/Images.swift (100%) rename libs/{swift => }/lume/src/Commands/List.swift (100%) rename libs/{swift => }/lume/src/Commands/Logs.swift (100%) rename libs/{swift => }/lume/src/Commands/Options/FormatOption.swift (100%) rename libs/{swift => }/lume/src/Commands/Prune.swift (100%) rename libs/{swift => }/lume/src/Commands/Pull.swift (100%) rename libs/{swift => }/lume/src/Commands/Push.swift (100%) rename libs/{swift => }/lume/src/Commands/Run.swift (100%) rename libs/{swift => }/lume/src/Commands/Serve.swift (100%) rename libs/{swift => }/lume/src/Commands/Set.swift (100%) rename libs/{swift => }/lume/src/Commands/Stop.swift (100%) rename libs/{swift => }/lume/src/ContainerRegistry/ImageContainerRegistry.swift (100%) rename libs/{swift => }/lume/src/ContainerRegistry/ImageList.swift (100%) rename libs/{swift => }/lume/src/ContainerRegistry/ImagesPrinter.swift (100%) rename libs/{swift => }/lume/src/Errors/Errors.swift (100%) rename libs/{swift => }/lume/src/FileSystem/Home.swift (100%) rename libs/{swift => }/lume/src/FileSystem/Settings.swift (100%) rename libs/{swift => }/lume/src/FileSystem/VMConfig.swift (100%) rename libs/{swift => }/lume/src/FileSystem/VMDirectory.swift (100%) rename libs/{swift => }/lume/src/FileSystem/VMLocation.swift (100%) rename libs/{swift => }/lume/src/LumeController.swift (100%) rename libs/{swift => }/lume/src/Main.swift (100%) rename libs/{swift => }/lume/src/Server/HTTP.swift (100%) rename libs/{swift => }/lume/src/Server/Handlers.swift (100%) rename libs/{swift => }/lume/src/Server/Requests.swift (100%) rename libs/{swift => }/lume/src/Server/Responses.swift (100%) rename libs/{swift => }/lume/src/Server/Server.swift (100%) rename libs/{swift => }/lume/src/Utils/CommandRegistry.swift (100%) rename libs/{swift => }/lume/src/Utils/CommandUtils.swift (100%) rename libs/{swift => }/lume/src/Utils/Logger.swift (100%) rename libs/{swift => }/lume/src/Utils/NetworkUtils.swift (100%) rename libs/{swift => }/lume/src/Utils/Path.swift (100%) rename libs/{swift => }/lume/src/Utils/ProcessRunner.swift (100%) rename libs/{swift => }/lume/src/Utils/ProgressLogger.swift (100%) rename libs/{swift => }/lume/src/Utils/String.swift (100%) rename libs/{swift => }/lume/src/Utils/Utils.swift (100%) rename libs/{swift => }/lume/src/VM/DarwinVM.swift (100%) rename libs/{swift => }/lume/src/VM/LinuxVM.swift (100%) rename libs/{swift => }/lume/src/VM/VM.swift (100%) rename libs/{swift => }/lume/src/VM/VMDetails.swift (100%) rename libs/{swift => }/lume/src/VM/VMDetailsPrinter.swift (100%) rename libs/{swift => }/lume/src/VM/VMDisplayResolution.swift (100%) rename libs/{swift => }/lume/src/VM/VMFactory.swift (100%) rename libs/{swift => }/lume/src/VNC/PassphraseGenerator.swift (100%) rename libs/{swift => }/lume/src/VNC/VNCService.swift (100%) rename libs/{swift => }/lume/src/Virtualization/DHCPLeaseParser.swift (100%) rename libs/{swift => }/lume/src/Virtualization/DarwinImageLoader.swift (100%) rename libs/{swift => }/lume/src/Virtualization/ImageLoaderFactory.swift (100%) rename libs/{swift => }/lume/src/Virtualization/VMVirtualizationService.swift (100%) rename libs/{swift => }/lume/tests/Mocks/MockVM.swift (100%) rename libs/{swift => }/lume/tests/Mocks/MockVMVirtualizationService.swift (100%) rename libs/{swift => }/lume/tests/Mocks/MockVNCService.swift (100%) rename libs/{swift => }/lume/tests/VM/VMDetailsPrinterTests.swift (100%) rename libs/{swift => }/lume/tests/VMTests.swift (100%) rename libs/{swift => }/lume/tests/VMVirtualizationServiceTests.swift (100%) rename libs/{swift => }/lume/tests/VNCServiceTests.swift (100%) rename libs/{bash => }/lumier/.dockerignore (100%) rename libs/{bash => }/lumier/Dockerfile (100%) rename libs/{bash => }/lumier/README.md (100%) rename libs/{bash => }/lumier/src/bin/entry.sh (100%) rename libs/{bash => }/lumier/src/config/constants.sh (100%) rename libs/{bash => }/lumier/src/hooks/on-logon.sh (100%) rename libs/{bash => }/lumier/src/lib/utils.sh (100%) rename libs/{bash => }/lumier/src/lib/vm.sh (100%) diff --git a/libs/swift/lume/.cursorignore b/libs/lume/.cursorignore similarity index 100% rename from libs/swift/lume/.cursorignore rename to libs/lume/.cursorignore diff --git a/libs/swift/lume/CONTRIBUTING.md b/libs/lume/CONTRIBUTING.md similarity index 100% rename from libs/swift/lume/CONTRIBUTING.md rename to libs/lume/CONTRIBUTING.md diff --git a/libs/swift/lume/Package.resolved b/libs/lume/Package.resolved similarity index 100% rename from libs/swift/lume/Package.resolved rename to libs/lume/Package.resolved diff --git a/libs/swift/lume/Package.swift b/libs/lume/Package.swift similarity index 100% rename from libs/swift/lume/Package.swift rename to libs/lume/Package.swift diff --git a/libs/swift/lume/README.md b/libs/lume/README.md similarity index 100% rename from libs/swift/lume/README.md rename to libs/lume/README.md diff --git a/libs/swift/lume/docs/API-Reference.md b/libs/lume/docs/API-Reference.md similarity index 100% rename from libs/swift/lume/docs/API-Reference.md rename to libs/lume/docs/API-Reference.md diff --git a/libs/swift/lume/docs/Development.md b/libs/lume/docs/Development.md similarity index 100% rename from libs/swift/lume/docs/Development.md rename to libs/lume/docs/Development.md diff --git a/libs/swift/lume/docs/FAQ.md b/libs/lume/docs/FAQ.md similarity index 100% rename from libs/swift/lume/docs/FAQ.md rename to libs/lume/docs/FAQ.md diff --git a/libs/swift/lume/img/cli.png b/libs/lume/img/cli.png similarity index 100% rename from libs/swift/lume/img/cli.png rename to libs/lume/img/cli.png diff --git a/libs/swift/lume/img/logo_black.png b/libs/lume/img/logo_black.png similarity index 100% rename from libs/swift/lume/img/logo_black.png rename to libs/lume/img/logo_black.png diff --git a/libs/swift/lume/img/logo_white.png b/libs/lume/img/logo_white.png similarity index 100% rename from libs/swift/lume/img/logo_white.png rename to libs/lume/img/logo_white.png diff --git a/libs/swift/lume/resources/lume.entitlements b/libs/lume/resources/lume.entitlements similarity index 100% rename from libs/swift/lume/resources/lume.entitlements rename to libs/lume/resources/lume.entitlements diff --git a/libs/lume/scripts/build/build-debug.sh b/libs/lume/scripts/build/build-debug.sh new file mode 100755 index 00000000..452e20c3 --- /dev/null +++ b/libs/lume/scripts/build/build-debug.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +swift build --product lume +codesign --force --entitlement resources/lume.entitlements --sign - .build/debug/lume diff --git a/libs/lume/scripts/build/build-release-notarized.sh b/libs/lume/scripts/build/build-release-notarized.sh new file mode 100755 index 00000000..603446b7 --- /dev/null +++ b/libs/lume/scripts/build/build-release-notarized.sh @@ -0,0 +1,211 @@ +#!/bin/bash + +# Set default log level if not provided +LOG_LEVEL=${LOG_LEVEL:-"normal"} + +# Function to log based on level +log() { + local level=$1 + local message=$2 + + case "$LOG_LEVEL" in + "minimal") + # Only show essential or error messages + if [ "$level" = "essential" ] || [ "$level" = "error" ]; then + echo "$message" + fi + ;; + "none") + # Show nothing except errors + if [ "$level" = "error" ]; then + echo "$message" >&2 + fi + ;; + *) + # Normal logging - show everything + echo "$message" + ;; + esac +} + +# Check required environment variables +required_vars=( + "CERT_APPLICATION_NAME" + "CERT_INSTALLER_NAME" + "APPLE_ID" + "TEAM_ID" + "APP_SPECIFIC_PASSWORD" +) + +for var in "${required_vars[@]}"; do + if [ -z "${!var}" ]; then + log "error" "Error: $var is not set" + exit 1 + fi +done + +# Get VERSION from environment or use default +VERSION=${VERSION:-"0.1.0"} + +# Move to the project root directory +pushd ../../ > /dev/null + +# Ensure .release directory exists and is clean +mkdir -p .release +log "normal" "Ensuring .release directory exists and is accessible" + +# Build the release version +log "essential" "Building release version..." +swift build -c release --product lume > /dev/null + +# Sign the binary with hardened runtime entitlements +log "essential" "Signing binary with entitlements..." +codesign --force --options runtime \ + --entitlement ./resources/lume.entitlements \ + --sign "$CERT_APPLICATION_NAME" \ + .build/release/lume 2> /dev/null + +# Create a temporary directory for packaging +TEMP_ROOT=$(mktemp -d) +mkdir -p "$TEMP_ROOT/usr/local/bin" +cp -f .build/release/lume "$TEMP_ROOT/usr/local/bin/" + +# Build the installer package +log "essential" "Building installer package..." +if ! pkgbuild --root "$TEMP_ROOT" \ + --identifier "com.trycua.lume" \ + --version "1.0" \ + --install-location "/" \ + --sign "$CERT_INSTALLER_NAME" \ + ./.release/lume.pkg; then + log "error" "Failed to build installer package" + exit 1 +fi + +# Verify the package was created +if [ ! -f "./.release/lume.pkg" ]; then + log "error" "Package file ./.release/lume.pkg was not created" + exit 1 +fi + +log "essential" "Package created successfully" + +# Submit for notarization using stored credentials +log "essential" "Submitting for notarization..." +if [ "$LOG_LEVEL" = "minimal" ] || [ "$LOG_LEVEL" = "none" ]; then + # Minimal output - capture ID but hide details + NOTARY_OUTPUT=$(xcrun notarytool submit ./.release/lume.pkg \ + --apple-id "${APPLE_ID}" \ + --team-id "${TEAM_ID}" \ + --password "${APP_SPECIFIC_PASSWORD}" \ + --wait 2>&1) + + # Check if notarization was successful + if echo "$NOTARY_OUTPUT" | grep -q "status: Accepted"; then + log "essential" "Notarization successful!" + else + log "error" "Notarization failed. Please check logs." + log "error" "Notarization output:" + echo "$NOTARY_OUTPUT" + exit 1 + fi +else + # Normal verbose output + if ! xcrun notarytool submit ./.release/lume.pkg \ + --apple-id "${APPLE_ID}" \ + --team-id "${TEAM_ID}" \ + --password "${APP_SPECIFIC_PASSWORD}" \ + --wait; then + log "error" "Notarization failed" + exit 1 + fi +fi + +# Staple the notarization ticket +log "essential" "Stapling notarization ticket..." +if ! xcrun stapler staple ./.release/lume.pkg > /dev/null 2>&1; then + log "error" "Failed to staple notarization ticket" + exit 1 +fi + +# Create temporary directory for package extraction +EXTRACT_ROOT=$(mktemp -d) +PKG_PATH="$(pwd)/.release/lume.pkg" + +# Extract the pkg using xar +cd "$EXTRACT_ROOT" +xar -xf "$PKG_PATH" > /dev/null 2>&1 + +# Verify Payload exists before proceeding +if [ ! -f "Payload" ]; then + log "error" "Error: Payload file not found after xar extraction" + exit 1 +fi + +# Create a directory for the extracted contents +mkdir -p extracted +cd extracted + +# Extract the Payload +cat ../Payload | gunzip -dc | cpio -i > /dev/null 2>&1 + +# Verify the binary exists +if [ ! -f "usr/local/bin/lume" ]; then + log "error" "Error: lume binary not found in expected location" + exit 1 +fi + +# Get the release directory absolute path +RELEASE_DIR="$(realpath "$(dirname "$PKG_PATH")")" +log "normal" "Using release directory: $RELEASE_DIR" + +# Copy extracted lume to the release directory +cp -f usr/local/bin/lume "$RELEASE_DIR/lume" + +# Install to user-local bin directory (standard location) +USER_BIN="$HOME/.local/bin" +mkdir -p "$USER_BIN" +cp -f "$RELEASE_DIR/lume" "$USER_BIN/lume" + +# Advise user to add to PATH if not present +if ! echo "$PATH" | grep -q "$USER_BIN"; then + log "normal" "[lume build] Note: $USER_BIN is not in your PATH. Add 'export PATH=\"$USER_BIN:\$PATH\"' to your shell profile." +fi + +# Get architecture and create OS identifier +ARCH=$(uname -m) +OS_IDENTIFIER="darwin-${ARCH}" + +# Create versioned archives of the package with OS identifier in the name +log "essential" "Creating archives in $RELEASE_DIR..." +cd "$RELEASE_DIR" + +# Clean up any existing artifacts first to avoid conflicts +rm -f lume-*.tar.gz lume-*.pkg.tar.gz + +# Create version-specific archives +log "essential" "Creating version-specific archives (${VERSION})..." +# Package the binary +tar -czf "lume-${VERSION}-${OS_IDENTIFIER}.tar.gz" lume > /dev/null 2>&1 +# Package the installer +tar -czf "lume-${VERSION}-${OS_IDENTIFIER}.pkg.tar.gz" lume.pkg > /dev/null 2>&1 + +# Create sha256 checksum file +log "essential" "Generating checksums..." +shasum -a 256 lume-*.tar.gz > checksums.txt +log "essential" "Package created successfully with checksums generated." + +# Show what's in the release directory +log "essential" "Files in release directory:" +ls -la "$RELEASE_DIR" + +# Ensure correct permissions +chmod 644 "$RELEASE_DIR"/*.tar.gz "$RELEASE_DIR"/*.pkg.tar.gz "$RELEASE_DIR"/checksums.txt + +popd > /dev/null + +# Clean up +rm -rf "$TEMP_ROOT" +rm -rf "$EXTRACT_ROOT" + +log "essential" "Build and packaging completed successfully." \ No newline at end of file diff --git a/libs/lume/scripts/build/build-release.sh b/libs/lume/scripts/build/build-release.sh new file mode 100755 index 00000000..20b63124 --- /dev/null +++ b/libs/lume/scripts/build/build-release.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +pushd ../../ + +swift build -c release --product lume +codesign --force --entitlement ./resources/lume.entitlements --sign - .build/release/lume + +mkdir -p ./.release +cp -f .build/release/lume ./.release/lume + +# Install to user-local bin directory (standard location) +USER_BIN="$HOME/.local/bin" +mkdir -p "$USER_BIN" +cp -f ./.release/lume "$USER_BIN/lume" + +# Advise user to add to PATH if not present +if ! echo "$PATH" | grep -q "$USER_BIN"; then + echo "[lume build] Note: $USER_BIN is not in your PATH. Add 'export PATH=\"$USER_BIN:\$PATH\"' to your shell profile." +fi + +popd \ No newline at end of file diff --git a/libs/swift/lume/scripts/install.sh b/libs/lume/scripts/install.sh similarity index 100% rename from libs/swift/lume/scripts/install.sh rename to libs/lume/scripts/install.sh diff --git a/libs/swift/lume/src/Commands/Clone.swift b/libs/lume/src/Commands/Clone.swift similarity index 100% rename from libs/swift/lume/src/Commands/Clone.swift rename to libs/lume/src/Commands/Clone.swift diff --git a/libs/swift/lume/src/Commands/Config.swift b/libs/lume/src/Commands/Config.swift similarity index 100% rename from libs/swift/lume/src/Commands/Config.swift rename to libs/lume/src/Commands/Config.swift diff --git a/libs/swift/lume/src/Commands/Create.swift b/libs/lume/src/Commands/Create.swift similarity index 100% rename from libs/swift/lume/src/Commands/Create.swift rename to libs/lume/src/Commands/Create.swift diff --git a/libs/swift/lume/src/Commands/Delete.swift b/libs/lume/src/Commands/Delete.swift similarity index 100% rename from libs/swift/lume/src/Commands/Delete.swift rename to libs/lume/src/Commands/Delete.swift diff --git a/libs/swift/lume/src/Commands/Get.swift b/libs/lume/src/Commands/Get.swift similarity index 100% rename from libs/swift/lume/src/Commands/Get.swift rename to libs/lume/src/Commands/Get.swift diff --git a/libs/swift/lume/src/Commands/IPSW.swift b/libs/lume/src/Commands/IPSW.swift similarity index 100% rename from libs/swift/lume/src/Commands/IPSW.swift rename to libs/lume/src/Commands/IPSW.swift diff --git a/libs/swift/lume/src/Commands/Images.swift b/libs/lume/src/Commands/Images.swift similarity index 100% rename from libs/swift/lume/src/Commands/Images.swift rename to libs/lume/src/Commands/Images.swift diff --git a/libs/swift/lume/src/Commands/List.swift b/libs/lume/src/Commands/List.swift similarity index 100% rename from libs/swift/lume/src/Commands/List.swift rename to libs/lume/src/Commands/List.swift diff --git a/libs/swift/lume/src/Commands/Logs.swift b/libs/lume/src/Commands/Logs.swift similarity index 100% rename from libs/swift/lume/src/Commands/Logs.swift rename to libs/lume/src/Commands/Logs.swift diff --git a/libs/swift/lume/src/Commands/Options/FormatOption.swift b/libs/lume/src/Commands/Options/FormatOption.swift similarity index 100% rename from libs/swift/lume/src/Commands/Options/FormatOption.swift rename to libs/lume/src/Commands/Options/FormatOption.swift diff --git a/libs/swift/lume/src/Commands/Prune.swift b/libs/lume/src/Commands/Prune.swift similarity index 100% rename from libs/swift/lume/src/Commands/Prune.swift rename to libs/lume/src/Commands/Prune.swift diff --git a/libs/swift/lume/src/Commands/Pull.swift b/libs/lume/src/Commands/Pull.swift similarity index 100% rename from libs/swift/lume/src/Commands/Pull.swift rename to libs/lume/src/Commands/Pull.swift diff --git a/libs/swift/lume/src/Commands/Push.swift b/libs/lume/src/Commands/Push.swift similarity index 100% rename from libs/swift/lume/src/Commands/Push.swift rename to libs/lume/src/Commands/Push.swift diff --git a/libs/swift/lume/src/Commands/Run.swift b/libs/lume/src/Commands/Run.swift similarity index 100% rename from libs/swift/lume/src/Commands/Run.swift rename to libs/lume/src/Commands/Run.swift diff --git a/libs/swift/lume/src/Commands/Serve.swift b/libs/lume/src/Commands/Serve.swift similarity index 100% rename from libs/swift/lume/src/Commands/Serve.swift rename to libs/lume/src/Commands/Serve.swift diff --git a/libs/swift/lume/src/Commands/Set.swift b/libs/lume/src/Commands/Set.swift similarity index 100% rename from libs/swift/lume/src/Commands/Set.swift rename to libs/lume/src/Commands/Set.swift diff --git a/libs/swift/lume/src/Commands/Stop.swift b/libs/lume/src/Commands/Stop.swift similarity index 100% rename from libs/swift/lume/src/Commands/Stop.swift rename to libs/lume/src/Commands/Stop.swift diff --git a/libs/swift/lume/src/ContainerRegistry/ImageContainerRegistry.swift b/libs/lume/src/ContainerRegistry/ImageContainerRegistry.swift similarity index 100% rename from libs/swift/lume/src/ContainerRegistry/ImageContainerRegistry.swift rename to libs/lume/src/ContainerRegistry/ImageContainerRegistry.swift diff --git a/libs/swift/lume/src/ContainerRegistry/ImageList.swift b/libs/lume/src/ContainerRegistry/ImageList.swift similarity index 100% rename from libs/swift/lume/src/ContainerRegistry/ImageList.swift rename to libs/lume/src/ContainerRegistry/ImageList.swift diff --git a/libs/swift/lume/src/ContainerRegistry/ImagesPrinter.swift b/libs/lume/src/ContainerRegistry/ImagesPrinter.swift similarity index 100% rename from libs/swift/lume/src/ContainerRegistry/ImagesPrinter.swift rename to libs/lume/src/ContainerRegistry/ImagesPrinter.swift diff --git a/libs/swift/lume/src/Errors/Errors.swift b/libs/lume/src/Errors/Errors.swift similarity index 100% rename from libs/swift/lume/src/Errors/Errors.swift rename to libs/lume/src/Errors/Errors.swift diff --git a/libs/swift/lume/src/FileSystem/Home.swift b/libs/lume/src/FileSystem/Home.swift similarity index 100% rename from libs/swift/lume/src/FileSystem/Home.swift rename to libs/lume/src/FileSystem/Home.swift diff --git a/libs/swift/lume/src/FileSystem/Settings.swift b/libs/lume/src/FileSystem/Settings.swift similarity index 100% rename from libs/swift/lume/src/FileSystem/Settings.swift rename to libs/lume/src/FileSystem/Settings.swift diff --git a/libs/swift/lume/src/FileSystem/VMConfig.swift b/libs/lume/src/FileSystem/VMConfig.swift similarity index 100% rename from libs/swift/lume/src/FileSystem/VMConfig.swift rename to libs/lume/src/FileSystem/VMConfig.swift diff --git a/libs/swift/lume/src/FileSystem/VMDirectory.swift b/libs/lume/src/FileSystem/VMDirectory.swift similarity index 100% rename from libs/swift/lume/src/FileSystem/VMDirectory.swift rename to libs/lume/src/FileSystem/VMDirectory.swift diff --git a/libs/swift/lume/src/FileSystem/VMLocation.swift b/libs/lume/src/FileSystem/VMLocation.swift similarity index 100% rename from libs/swift/lume/src/FileSystem/VMLocation.swift rename to libs/lume/src/FileSystem/VMLocation.swift diff --git a/libs/swift/lume/src/LumeController.swift b/libs/lume/src/LumeController.swift similarity index 100% rename from libs/swift/lume/src/LumeController.swift rename to libs/lume/src/LumeController.swift diff --git a/libs/swift/lume/src/Main.swift b/libs/lume/src/Main.swift similarity index 100% rename from libs/swift/lume/src/Main.swift rename to libs/lume/src/Main.swift diff --git a/libs/swift/lume/src/Server/HTTP.swift b/libs/lume/src/Server/HTTP.swift similarity index 100% rename from libs/swift/lume/src/Server/HTTP.swift rename to libs/lume/src/Server/HTTP.swift diff --git a/libs/swift/lume/src/Server/Handlers.swift b/libs/lume/src/Server/Handlers.swift similarity index 100% rename from libs/swift/lume/src/Server/Handlers.swift rename to libs/lume/src/Server/Handlers.swift diff --git a/libs/swift/lume/src/Server/Requests.swift b/libs/lume/src/Server/Requests.swift similarity index 100% rename from libs/swift/lume/src/Server/Requests.swift rename to libs/lume/src/Server/Requests.swift diff --git a/libs/swift/lume/src/Server/Responses.swift b/libs/lume/src/Server/Responses.swift similarity index 100% rename from libs/swift/lume/src/Server/Responses.swift rename to libs/lume/src/Server/Responses.swift diff --git a/libs/swift/lume/src/Server/Server.swift b/libs/lume/src/Server/Server.swift similarity index 100% rename from libs/swift/lume/src/Server/Server.swift rename to libs/lume/src/Server/Server.swift diff --git a/libs/swift/lume/src/Utils/CommandRegistry.swift b/libs/lume/src/Utils/CommandRegistry.swift similarity index 100% rename from libs/swift/lume/src/Utils/CommandRegistry.swift rename to libs/lume/src/Utils/CommandRegistry.swift diff --git a/libs/swift/lume/src/Utils/CommandUtils.swift b/libs/lume/src/Utils/CommandUtils.swift similarity index 100% rename from libs/swift/lume/src/Utils/CommandUtils.swift rename to libs/lume/src/Utils/CommandUtils.swift diff --git a/libs/swift/lume/src/Utils/Logger.swift b/libs/lume/src/Utils/Logger.swift similarity index 100% rename from libs/swift/lume/src/Utils/Logger.swift rename to libs/lume/src/Utils/Logger.swift diff --git a/libs/swift/lume/src/Utils/NetworkUtils.swift b/libs/lume/src/Utils/NetworkUtils.swift similarity index 100% rename from libs/swift/lume/src/Utils/NetworkUtils.swift rename to libs/lume/src/Utils/NetworkUtils.swift diff --git a/libs/swift/lume/src/Utils/Path.swift b/libs/lume/src/Utils/Path.swift similarity index 100% rename from libs/swift/lume/src/Utils/Path.swift rename to libs/lume/src/Utils/Path.swift diff --git a/libs/swift/lume/src/Utils/ProcessRunner.swift b/libs/lume/src/Utils/ProcessRunner.swift similarity index 100% rename from libs/swift/lume/src/Utils/ProcessRunner.swift rename to libs/lume/src/Utils/ProcessRunner.swift diff --git a/libs/swift/lume/src/Utils/ProgressLogger.swift b/libs/lume/src/Utils/ProgressLogger.swift similarity index 100% rename from libs/swift/lume/src/Utils/ProgressLogger.swift rename to libs/lume/src/Utils/ProgressLogger.swift diff --git a/libs/swift/lume/src/Utils/String.swift b/libs/lume/src/Utils/String.swift similarity index 100% rename from libs/swift/lume/src/Utils/String.swift rename to libs/lume/src/Utils/String.swift diff --git a/libs/swift/lume/src/Utils/Utils.swift b/libs/lume/src/Utils/Utils.swift similarity index 100% rename from libs/swift/lume/src/Utils/Utils.swift rename to libs/lume/src/Utils/Utils.swift diff --git a/libs/swift/lume/src/VM/DarwinVM.swift b/libs/lume/src/VM/DarwinVM.swift similarity index 100% rename from libs/swift/lume/src/VM/DarwinVM.swift rename to libs/lume/src/VM/DarwinVM.swift diff --git a/libs/swift/lume/src/VM/LinuxVM.swift b/libs/lume/src/VM/LinuxVM.swift similarity index 100% rename from libs/swift/lume/src/VM/LinuxVM.swift rename to libs/lume/src/VM/LinuxVM.swift diff --git a/libs/swift/lume/src/VM/VM.swift b/libs/lume/src/VM/VM.swift similarity index 100% rename from libs/swift/lume/src/VM/VM.swift rename to libs/lume/src/VM/VM.swift diff --git a/libs/swift/lume/src/VM/VMDetails.swift b/libs/lume/src/VM/VMDetails.swift similarity index 100% rename from libs/swift/lume/src/VM/VMDetails.swift rename to libs/lume/src/VM/VMDetails.swift diff --git a/libs/swift/lume/src/VM/VMDetailsPrinter.swift b/libs/lume/src/VM/VMDetailsPrinter.swift similarity index 100% rename from libs/swift/lume/src/VM/VMDetailsPrinter.swift rename to libs/lume/src/VM/VMDetailsPrinter.swift diff --git a/libs/swift/lume/src/VM/VMDisplayResolution.swift b/libs/lume/src/VM/VMDisplayResolution.swift similarity index 100% rename from libs/swift/lume/src/VM/VMDisplayResolution.swift rename to libs/lume/src/VM/VMDisplayResolution.swift diff --git a/libs/swift/lume/src/VM/VMFactory.swift b/libs/lume/src/VM/VMFactory.swift similarity index 100% rename from libs/swift/lume/src/VM/VMFactory.swift rename to libs/lume/src/VM/VMFactory.swift diff --git a/libs/swift/lume/src/VNC/PassphraseGenerator.swift b/libs/lume/src/VNC/PassphraseGenerator.swift similarity index 100% rename from libs/swift/lume/src/VNC/PassphraseGenerator.swift rename to libs/lume/src/VNC/PassphraseGenerator.swift diff --git a/libs/swift/lume/src/VNC/VNCService.swift b/libs/lume/src/VNC/VNCService.swift similarity index 100% rename from libs/swift/lume/src/VNC/VNCService.swift rename to libs/lume/src/VNC/VNCService.swift diff --git a/libs/swift/lume/src/Virtualization/DHCPLeaseParser.swift b/libs/lume/src/Virtualization/DHCPLeaseParser.swift similarity index 100% rename from libs/swift/lume/src/Virtualization/DHCPLeaseParser.swift rename to libs/lume/src/Virtualization/DHCPLeaseParser.swift diff --git a/libs/swift/lume/src/Virtualization/DarwinImageLoader.swift b/libs/lume/src/Virtualization/DarwinImageLoader.swift similarity index 100% rename from libs/swift/lume/src/Virtualization/DarwinImageLoader.swift rename to libs/lume/src/Virtualization/DarwinImageLoader.swift diff --git a/libs/swift/lume/src/Virtualization/ImageLoaderFactory.swift b/libs/lume/src/Virtualization/ImageLoaderFactory.swift similarity index 100% rename from libs/swift/lume/src/Virtualization/ImageLoaderFactory.swift rename to libs/lume/src/Virtualization/ImageLoaderFactory.swift diff --git a/libs/swift/lume/src/Virtualization/VMVirtualizationService.swift b/libs/lume/src/Virtualization/VMVirtualizationService.swift similarity index 100% rename from libs/swift/lume/src/Virtualization/VMVirtualizationService.swift rename to libs/lume/src/Virtualization/VMVirtualizationService.swift diff --git a/libs/swift/lume/tests/Mocks/MockVM.swift b/libs/lume/tests/Mocks/MockVM.swift similarity index 100% rename from libs/swift/lume/tests/Mocks/MockVM.swift rename to libs/lume/tests/Mocks/MockVM.swift diff --git a/libs/swift/lume/tests/Mocks/MockVMVirtualizationService.swift b/libs/lume/tests/Mocks/MockVMVirtualizationService.swift similarity index 100% rename from libs/swift/lume/tests/Mocks/MockVMVirtualizationService.swift rename to libs/lume/tests/Mocks/MockVMVirtualizationService.swift diff --git a/libs/swift/lume/tests/Mocks/MockVNCService.swift b/libs/lume/tests/Mocks/MockVNCService.swift similarity index 100% rename from libs/swift/lume/tests/Mocks/MockVNCService.swift rename to libs/lume/tests/Mocks/MockVNCService.swift diff --git a/libs/swift/lume/tests/VM/VMDetailsPrinterTests.swift b/libs/lume/tests/VM/VMDetailsPrinterTests.swift similarity index 100% rename from libs/swift/lume/tests/VM/VMDetailsPrinterTests.swift rename to libs/lume/tests/VM/VMDetailsPrinterTests.swift diff --git a/libs/swift/lume/tests/VMTests.swift b/libs/lume/tests/VMTests.swift similarity index 100% rename from libs/swift/lume/tests/VMTests.swift rename to libs/lume/tests/VMTests.swift diff --git a/libs/swift/lume/tests/VMVirtualizationServiceTests.swift b/libs/lume/tests/VMVirtualizationServiceTests.swift similarity index 100% rename from libs/swift/lume/tests/VMVirtualizationServiceTests.swift rename to libs/lume/tests/VMVirtualizationServiceTests.swift diff --git a/libs/swift/lume/tests/VNCServiceTests.swift b/libs/lume/tests/VNCServiceTests.swift similarity index 100% rename from libs/swift/lume/tests/VNCServiceTests.swift rename to libs/lume/tests/VNCServiceTests.swift diff --git a/libs/bash/lumier/.dockerignore b/libs/lumier/.dockerignore similarity index 100% rename from libs/bash/lumier/.dockerignore rename to libs/lumier/.dockerignore diff --git a/libs/bash/lumier/Dockerfile b/libs/lumier/Dockerfile similarity index 100% rename from libs/bash/lumier/Dockerfile rename to libs/lumier/Dockerfile diff --git a/libs/bash/lumier/README.md b/libs/lumier/README.md similarity index 100% rename from libs/bash/lumier/README.md rename to libs/lumier/README.md diff --git a/libs/bash/lumier/src/bin/entry.sh b/libs/lumier/src/bin/entry.sh similarity index 100% rename from libs/bash/lumier/src/bin/entry.sh rename to libs/lumier/src/bin/entry.sh diff --git a/libs/bash/lumier/src/config/constants.sh b/libs/lumier/src/config/constants.sh similarity index 100% rename from libs/bash/lumier/src/config/constants.sh rename to libs/lumier/src/config/constants.sh diff --git a/libs/bash/lumier/src/hooks/on-logon.sh b/libs/lumier/src/hooks/on-logon.sh similarity index 100% rename from libs/bash/lumier/src/hooks/on-logon.sh rename to libs/lumier/src/hooks/on-logon.sh diff --git a/libs/bash/lumier/src/lib/utils.sh b/libs/lumier/src/lib/utils.sh similarity index 100% rename from libs/bash/lumier/src/lib/utils.sh rename to libs/lumier/src/lib/utils.sh diff --git a/libs/bash/lumier/src/lib/vm.sh b/libs/lumier/src/lib/vm.sh similarity index 100% rename from libs/bash/lumier/src/lib/vm.sh rename to libs/lumier/src/lib/vm.sh From b0666df203879835bf8154e2bdc73c78442d9c47 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Mon, 30 Jun 2025 10:14:52 -0700 Subject: [PATCH 113/141] Rename @cua/ to @trycua/, move ts example to ./examples, rename workflows for better organization --- .github/workflows/ci-lume.yml | 4 +- ...mputer-ts.yml => npm-publish-computer.yml} | 6 +- ...blish-core-ts.yml => npm-publish-core.yml} | 6 +- .github/workflows/publish-lume.yml | 26 +- ...blish-agent.yml => pypi-publish-agent.yml} | 2 +- ...r.yml => pypi-publish-computer-server.yml} | 2 +- ...computer.yml => pypi-publish-computer.yml} | 2 +- ...publish-core.yml => pypi-publish-core.yml} | 2 +- ...server.yml => pypi-publish-mcp-server.yml} | 2 +- ...ish-pylume.yml => pypi-publish-pylume.yml} | 2 +- .../{publish-som.yml => pypi-publish-som.yml} | 2 +- ...-publish.yml => pypi-reusable-publish.yml} | 0 .../computer-example-ts}/.env.example | 0 .../computer-example-ts}/.gitignore | 0 .../computer-example-ts}/.prettierrc | 0 .../computer-example-ts}/README.md | 6 +- .../computer-example-ts}/package.json | 2 +- examples/computer-example-ts/pnpm-lock.yaml | 362 ++++++++++++++++++ examples/computer-example-ts/src/helpers.ts | 63 +++ examples/computer-example-ts/src/index.ts | 104 +++++ .../computer-example-ts}/tsconfig.json | 0 libs/typescript/README.md | 16 +- libs/typescript/computer/README.md | 6 +- libs/typescript/computer/package.json | 4 +- .../computer/src/computer/providers/base.ts | 194 +++++----- libs/typescript/core/package.json | 2 +- .../examples/cua-cloud-openai/src/helpers.ts | 56 --- .../examples/cua-cloud-openai/src/index.ts | 104 ----- libs/typescript/package.json | 8 +- libs/typescript/pnpm-lock.yaml | 358 +++++++---------- 30 files changed, 825 insertions(+), 516 deletions(-) rename .github/workflows/{publish-computer-ts.yml => npm-publish-computer.yml} (85%) rename .github/workflows/{publish-core-ts.yml => npm-publish-core.yml} (85%) rename .github/workflows/{publish-agent.yml => pypi-publish-agent.yml} (99%) rename .github/workflows/{publish-computer-server.yml => pypi-publish-computer-server.yml} (97%) rename .github/workflows/{publish-computer.yml => pypi-publish-computer.yml} (98%) rename .github/workflows/{publish-core.yml => pypi-publish-core.yml} (96%) rename .github/workflows/{publish-mcp-server.yml => pypi-publish-mcp-server.yml} (99%) rename .github/workflows/{publish-pylume.yml => pypi-publish-pylume.yml} (97%) rename .github/workflows/{publish-som.yml => pypi-publish-som.yml} (97%) rename .github/workflows/{reusable-publish.yml => pypi-reusable-publish.yml} (100%) rename {libs/typescript/examples/cua-cloud-openai => examples/computer-example-ts}/.env.example (100%) rename {libs/typescript/examples/cua-cloud-openai => examples/computer-example-ts}/.gitignore (100%) rename {libs/typescript/examples/cua-cloud-openai => examples/computer-example-ts}/.prettierrc (100%) rename {libs/typescript/examples/cua-cloud-openai => examples/computer-example-ts}/README.md (87%) rename {libs/typescript/examples/cua-cloud-openai => examples/computer-example-ts}/package.json (88%) create mode 100644 examples/computer-example-ts/pnpm-lock.yaml create mode 100644 examples/computer-example-ts/src/helpers.ts create mode 100644 examples/computer-example-ts/src/index.ts rename {libs/typescript/examples/cua-cloud-openai => examples/computer-example-ts}/tsconfig.json (100%) delete mode 100644 libs/typescript/examples/cua-cloud-openai/src/helpers.ts delete mode 100644 libs/typescript/examples/cua-cloud-openai/src/index.ts diff --git a/.github/workflows/ci-lume.yml b/.github/workflows/ci-lume.yml index dc31bd5a..d33191cc 100644 --- a/.github/workflows/ci-lume.yml +++ b/.github/workflows/ci-lume.yml @@ -20,7 +20,7 @@ jobs: - run: uname -a - run: sudo xcode-select -s /Applications/Xcode_16.app # Swift 6.0 - run: swift test - working-directory: ./libs/swift/lume + working-directory: ./libs/lume build: name: Release build runs-on: macos-15 @@ -29,4 +29,4 @@ jobs: - run: uname -a - run: sudo xcode-select -s /Applications/Xcode_16.app # Swift 6.0 - run: swift build --configuration release - working-directory: ./libs/swift/lume + working-directory: ./libs/lume diff --git a/.github/workflows/publish-computer-ts.yml b/.github/workflows/npm-publish-computer.yml similarity index 85% rename from .github/workflows/publish-computer-ts.yml rename to .github/workflows/npm-publish-computer.yml index 6d9f72d9..2f67439b 100644 --- a/.github/workflows/publish-computer-ts.yml +++ b/.github/workflows/npm-publish-computer.yml @@ -1,9 +1,9 @@ -name: Publish @cua/computer to npm +name: Publish @trycua/computer to npm on: push: tags: - - 'computer-v*' + - "computer-v*" jobs: publish: @@ -15,7 +15,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: lts/* - registry-url: 'https://registry.npmjs.org' + registry-url: "https://registry.npmjs.org" - name: Install dependencies working-directory: ./libs/typescript/computer diff --git a/.github/workflows/publish-core-ts.yml b/.github/workflows/npm-publish-core.yml similarity index 85% rename from .github/workflows/publish-core-ts.yml rename to .github/workflows/npm-publish-core.yml index 61e26283..e7bd996b 100644 --- a/.github/workflows/publish-core-ts.yml +++ b/.github/workflows/npm-publish-core.yml @@ -1,9 +1,9 @@ -name: Publish @cua/core to npm +name: Publish @trycua/core to npm on: push: tags: - - 'core-v*' + - "core-v*" jobs: publish: @@ -15,7 +15,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: lts/* - registry-url: 'https://registry.npmjs.org' + registry-url: "https://registry.npmjs.org" - name: Install dependencies working-directory: ./libs/typescript/core diff --git a/.github/workflows/publish-lume.yml b/.github/workflows/publish-lume.yml index 8557d516..b2ea3c4b 100644 --- a/.github/workflows/publish-lume.yml +++ b/.github/workflows/publish-lume.yml @@ -85,7 +85,7 @@ jobs: # Update version in Main.swift echo "Updating version in Main.swift to $VERSION" - sed -i '' "s/static let current: String = \".*\"/static let current: String = \"$VERSION\"/" libs/swift/lume/src/Main.swift + sed -i '' "s/static let current: String = \".*\"/static let current: String = \"$VERSION\"/" libs/lume/src/Main.swift # Set output for later steps echo "version=$VERSION" >> $GITHUB_OUTPUT @@ -147,7 +147,7 @@ jobs: CERT_APPLICATION_NAME: "Developer ID Application: ${{ secrets.DEVELOPER_NAME }} (${{ secrets.TEAM_ID }})" CERT_INSTALLER_NAME: "Developer ID Installer: ${{ secrets.DEVELOPER_NAME }} (${{ secrets.TEAM_ID }})" VERSION: ${{ steps.set_version.outputs.version }} - working-directory: ./libs/swift/lume + working-directory: ./libs/lume run: | # Minimal debug information echo "Starting build process..." @@ -185,7 +185,7 @@ jobs: - name: Generate SHA256 Checksums id: generate_checksums - working-directory: ./libs/swift/lume/.release + working-directory: ./libs/lume/.release run: | # Use existing checksums file if it exists, otherwise generate one if [ -f "checksums.txt" ]; then @@ -208,7 +208,7 @@ jobs: ls -la - name: Create Standard Version Releases - working-directory: ./libs/swift/lume/.release + working-directory: ./libs/lume/.release run: | VERSION=${{ steps.set_version.outputs.version }} ARCH=$(uname -m) @@ -230,14 +230,14 @@ jobs: uses: actions/upload-artifact@v4 with: name: lume-notarized-tarball - path: ./libs/swift/lume/${{ steps.build_notarize.outputs.tarball_path }} + path: ./libs/lume/${{ steps.build_notarize.outputs.tarball_path }} if-no-files-found: error - name: Upload Notarized Package (Installer) uses: actions/upload-artifact@v4 with: name: lume-notarized-installer - path: ./libs/swift/lume/${{ steps.build_notarize.outputs.pkg_path }} + path: ./libs/lume/${{ steps.build_notarize.outputs.pkg_path }} if-no-files-found: error - name: Create Release @@ -245,18 +245,18 @@ jobs: uses: softprops/action-gh-release@v1 with: files: | - ./libs/swift/lume/${{ steps.build_notarize.outputs.tarball_path }} - ./libs/swift/lume/${{ steps.build_notarize.outputs.pkg_path }} - ./libs/swift/lume/.release/lume-darwin.tar.gz - ./libs/swift/lume/.release/lume-darwin.pkg.tar.gz - ./libs/swift/lume/.release/lume.tar.gz - ./libs/swift/lume/.release/lume.pkg.tar.gz + ./libs/lume/${{ steps.build_notarize.outputs.tarball_path }} + ./libs/lume/${{ steps.build_notarize.outputs.pkg_path }} + ./libs/lume/.release/lume-darwin.tar.gz + ./libs/lume/.release/lume-darwin.pkg.tar.gz + ./libs/lume/.release/lume.tar.gz + ./libs/lume/.release/lume.pkg.tar.gz body: | ${{ steps.generate_checksums.outputs.checksums }} ### Installation with script - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/libs/swift/lume/scripts/install.sh)" + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/libs/lume/scripts/install.sh)" ``` generate_release_notes: true make_latest: true diff --git a/.github/workflows/publish-agent.yml b/.github/workflows/pypi-publish-agent.yml similarity index 99% rename from .github/workflows/publish-agent.yml rename to .github/workflows/pypi-publish-agent.yml index 5869bcd1..c36c1c1b 100644 --- a/.github/workflows/publish-agent.yml +++ b/.github/workflows/pypi-publish-agent.yml @@ -141,7 +141,7 @@ jobs: publish: needs: prepare - uses: ./.github/workflows/reusable-publish.yml + uses: ./.github/workflows/pypi-reusable-publish.yml with: package_name: "agent" package_dir: "libs/python/agent" diff --git a/.github/workflows/publish-computer-server.yml b/.github/workflows/pypi-publish-computer-server.yml similarity index 97% rename from .github/workflows/publish-computer-server.yml rename to .github/workflows/pypi-publish-computer-server.yml index 01f61530..899cc605 100644 --- a/.github/workflows/publish-computer-server.yml +++ b/.github/workflows/pypi-publish-computer-server.yml @@ -61,7 +61,7 @@ jobs: publish: needs: prepare - uses: ./.github/workflows/reusable-publish.yml + uses: ./.github/workflows/pypi-reusable-publish.yml with: package_name: "computer-server" package_dir: "libs/python/computer-server" diff --git a/.github/workflows/publish-computer.yml b/.github/workflows/pypi-publish-computer.yml similarity index 98% rename from .github/workflows/publish-computer.yml rename to .github/workflows/pypi-publish-computer.yml index df13669d..ea5d644b 100644 --- a/.github/workflows/publish-computer.yml +++ b/.github/workflows/pypi-publish-computer.yml @@ -121,7 +121,7 @@ jobs: publish: needs: prepare - uses: ./.github/workflows/reusable-publish.yml + uses: ./.github/workflows/pypi-reusable-publish.yml with: package_name: "computer" package_dir: "libs/python/computer" diff --git a/.github/workflows/publish-core.yml b/.github/workflows/pypi-publish-core.yml similarity index 96% rename from .github/workflows/publish-core.yml rename to .github/workflows/pypi-publish-core.yml index 760c055d..a5b993f6 100644 --- a/.github/workflows/publish-core.yml +++ b/.github/workflows/pypi-publish-core.yml @@ -52,7 +52,7 @@ jobs: publish: needs: prepare - uses: ./.github/workflows/reusable-publish.yml + uses: ./.github/workflows/pypi-reusable-publish.yml with: package_name: "core" package_dir: "libs/python/core" diff --git a/.github/workflows/publish-mcp-server.yml b/.github/workflows/pypi-publish-mcp-server.yml similarity index 99% rename from .github/workflows/publish-mcp-server.yml rename to .github/workflows/pypi-publish-mcp-server.yml index 0e84a5a6..cd1b0c2f 100644 --- a/.github/workflows/publish-mcp-server.yml +++ b/.github/workflows/pypi-publish-mcp-server.yml @@ -137,7 +137,7 @@ jobs: publish: needs: prepare - uses: ./.github/workflows/reusable-publish.yml + uses: ./.github/workflows/pypi-reusable-publish.yml with: package_name: "mcp-server" package_dir: "libs/python/mcp-server" diff --git a/.github/workflows/publish-pylume.yml b/.github/workflows/pypi-publish-pylume.yml similarity index 97% rename from .github/workflows/publish-pylume.yml rename to .github/workflows/pypi-publish-pylume.yml index e9e52539..91278c00 100644 --- a/.github/workflows/publish-pylume.yml +++ b/.github/workflows/pypi-publish-pylume.yml @@ -71,7 +71,7 @@ jobs: publish: needs: determine-version - uses: ./.github/workflows/reusable-publish.yml + uses: ./.github/workflows/pypi-reusable-publish.yml with: package_name: "pylume" package_dir: "libs/python/pylume" diff --git a/.github/workflows/publish-som.yml b/.github/workflows/pypi-publish-som.yml similarity index 97% rename from .github/workflows/publish-som.yml rename to .github/workflows/pypi-publish-som.yml index a5ac30e9..fd80d3e8 100644 --- a/.github/workflows/publish-som.yml +++ b/.github/workflows/pypi-publish-som.yml @@ -56,7 +56,7 @@ jobs: publish: needs: determine-version - uses: ./.github/workflows/reusable-publish.yml + uses: ./.github/workflows/pypi-reusable-publish.yml with: package_name: "som" package_dir: "libs/python/som" diff --git a/.github/workflows/reusable-publish.yml b/.github/workflows/pypi-reusable-publish.yml similarity index 100% rename from .github/workflows/reusable-publish.yml rename to .github/workflows/pypi-reusable-publish.yml diff --git a/libs/typescript/examples/cua-cloud-openai/.env.example b/examples/computer-example-ts/.env.example similarity index 100% rename from libs/typescript/examples/cua-cloud-openai/.env.example rename to examples/computer-example-ts/.env.example diff --git a/libs/typescript/examples/cua-cloud-openai/.gitignore b/examples/computer-example-ts/.gitignore similarity index 100% rename from libs/typescript/examples/cua-cloud-openai/.gitignore rename to examples/computer-example-ts/.gitignore diff --git a/libs/typescript/examples/cua-cloud-openai/.prettierrc b/examples/computer-example-ts/.prettierrc similarity index 100% rename from libs/typescript/examples/cua-cloud-openai/.prettierrc rename to examples/computer-example-ts/.prettierrc diff --git a/libs/typescript/examples/cua-cloud-openai/README.md b/examples/computer-example-ts/README.md similarity index 87% rename from libs/typescript/examples/cua-cloud-openai/README.md rename to examples/computer-example-ts/README.md index 3f4fe8c1..1e61eab0 100644 --- a/libs/typescript/examples/cua-cloud-openai/README.md +++ b/examples/computer-example-ts/README.md @@ -1,10 +1,10 @@ # cua-cloud-openai Example -This example demonstrates how to control a c/ua Cloud container using the OpenAI `computer-use-preview` model and the `@cua/computer` TypeScript library. +This example demonstrates how to control a c/ua Cloud container using the OpenAI `computer-use-preview` model and the `@trycua/computer` TypeScript library. ## Overview -- Connects to a c/ua Cloud container via the `@cua/computer` library +- Connects to a c/ua Cloud container via the `@trycua/computer` library - Sends screenshots and instructions to OpenAI's computer-use model - Executes AI-generated actions (clicks, typing, etc.) inside the container - Designed for Linux containers, but can be adapted for other OS types @@ -12,6 +12,7 @@ This example demonstrates how to control a c/ua Cloud container using the OpenAI ## Getting Started 1. **Install dependencies:** + ```bash npm install ``` @@ -23,6 +24,7 @@ This example demonstrates how to control a c/ua Cloud container using the OpenAI - `CUA_CONTAINER_NAME` — the name of your provisioned container 3. **Run the example:** + ```bash npx tsx src/index.ts ``` diff --git a/libs/typescript/examples/cua-cloud-openai/package.json b/examples/computer-example-ts/package.json similarity index 88% rename from libs/typescript/examples/cua-cloud-openai/package.json rename to examples/computer-example-ts/package.json index 3d769cb0..4e961528 100644 --- a/libs/typescript/examples/cua-cloud-openai/package.json +++ b/examples/computer-example-ts/package.json @@ -13,7 +13,7 @@ "license": "MIT", "packageManager": "pnpm@10.12.3", "dependencies": { - "@cua/computer": "link:../../computer", + "@trycua/computer": "link:../../libs/typescript/computer", "dotenv": "^16.5.0", "openai": "^5.7.0" }, diff --git a/examples/computer-example-ts/pnpm-lock.yaml b/examples/computer-example-ts/pnpm-lock.yaml new file mode 100644 index 00000000..6d0882b3 --- /dev/null +++ b/examples/computer-example-ts/pnpm-lock.yaml @@ -0,0 +1,362 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@trycua/computer': + specifier: link:../../libs/typescript/computer + version: link:../../libs/typescript/computer + dotenv: + specifier: ^16.5.0 + version: 16.6.1 + openai: + specifier: ^5.7.0 + version: 5.8.2 + devDependencies: + '@types/node': + specifier: ^22.15.33 + version: 22.15.34 + tsx: + specifier: ^4.20.3 + version: 4.20.3 + typescript: + specifier: ^5.8.3 + version: 5.8.3 + +packages: + + '@esbuild/aix-ppc64@0.25.5': + resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.5': + resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.5': + resolution: {integrity: sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.5': + resolution: {integrity: sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.5': + resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.5': + resolution: {integrity: sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.5': + resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.5': + resolution: {integrity: sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.5': + resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.5': + resolution: {integrity: sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.5': + resolution: {integrity: sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.5': + resolution: {integrity: sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.5': + resolution: {integrity: sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.5': + resolution: {integrity: sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.5': + resolution: {integrity: sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.5': + resolution: {integrity: sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.5': + resolution: {integrity: sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.5': + resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.5': + resolution: {integrity: sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.5': + resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.5': + resolution: {integrity: sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.25.5': + resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.5': + resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.5': + resolution: {integrity: sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.5': + resolution: {integrity: sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@types/node@22.15.34': + resolution: {integrity: sha512-8Y6E5WUupYy1Dd0II32BsWAx5MWdcnRd8L84Oys3veg1YrYtNtzgO4CFhiBg6MDSjk7Ay36HYOnU7/tuOzIzcw==} + + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + esbuild@0.25.5: + resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} + engines: {node: '>=18'} + hasBin: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-tsconfig@4.10.1: + resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + + openai@5.8.2: + resolution: {integrity: sha512-8C+nzoHYgyYOXhHGN6r0fcb4SznuEn1R7YZMvlqDbnCuE0FM2mm3T1HiYW6WIcMS/F1Of2up/cSPjLPaWt0X9Q==} + hasBin: true + peerDependencies: + ws: ^8.18.0 + zod: ^3.23.8 + peerDependenciesMeta: + ws: + optional: true + zod: + optional: true + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + tsx@4.20.3: + resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==} + engines: {node: '>=18.0.0'} + hasBin: true + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + +snapshots: + + '@esbuild/aix-ppc64@0.25.5': + optional: true + + '@esbuild/android-arm64@0.25.5': + optional: true + + '@esbuild/android-arm@0.25.5': + optional: true + + '@esbuild/android-x64@0.25.5': + optional: true + + '@esbuild/darwin-arm64@0.25.5': + optional: true + + '@esbuild/darwin-x64@0.25.5': + optional: true + + '@esbuild/freebsd-arm64@0.25.5': + optional: true + + '@esbuild/freebsd-x64@0.25.5': + optional: true + + '@esbuild/linux-arm64@0.25.5': + optional: true + + '@esbuild/linux-arm@0.25.5': + optional: true + + '@esbuild/linux-ia32@0.25.5': + optional: true + + '@esbuild/linux-loong64@0.25.5': + optional: true + + '@esbuild/linux-mips64el@0.25.5': + optional: true + + '@esbuild/linux-ppc64@0.25.5': + optional: true + + '@esbuild/linux-riscv64@0.25.5': + optional: true + + '@esbuild/linux-s390x@0.25.5': + optional: true + + '@esbuild/linux-x64@0.25.5': + optional: true + + '@esbuild/netbsd-arm64@0.25.5': + optional: true + + '@esbuild/netbsd-x64@0.25.5': + optional: true + + '@esbuild/openbsd-arm64@0.25.5': + optional: true + + '@esbuild/openbsd-x64@0.25.5': + optional: true + + '@esbuild/sunos-x64@0.25.5': + optional: true + + '@esbuild/win32-arm64@0.25.5': + optional: true + + '@esbuild/win32-ia32@0.25.5': + optional: true + + '@esbuild/win32-x64@0.25.5': + optional: true + + '@types/node@22.15.34': + dependencies: + undici-types: 6.21.0 + + dotenv@16.6.1: {} + + esbuild@0.25.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.5 + '@esbuild/android-arm': 0.25.5 + '@esbuild/android-arm64': 0.25.5 + '@esbuild/android-x64': 0.25.5 + '@esbuild/darwin-arm64': 0.25.5 + '@esbuild/darwin-x64': 0.25.5 + '@esbuild/freebsd-arm64': 0.25.5 + '@esbuild/freebsd-x64': 0.25.5 + '@esbuild/linux-arm': 0.25.5 + '@esbuild/linux-arm64': 0.25.5 + '@esbuild/linux-ia32': 0.25.5 + '@esbuild/linux-loong64': 0.25.5 + '@esbuild/linux-mips64el': 0.25.5 + '@esbuild/linux-ppc64': 0.25.5 + '@esbuild/linux-riscv64': 0.25.5 + '@esbuild/linux-s390x': 0.25.5 + '@esbuild/linux-x64': 0.25.5 + '@esbuild/netbsd-arm64': 0.25.5 + '@esbuild/netbsd-x64': 0.25.5 + '@esbuild/openbsd-arm64': 0.25.5 + '@esbuild/openbsd-x64': 0.25.5 + '@esbuild/sunos-x64': 0.25.5 + '@esbuild/win32-arm64': 0.25.5 + '@esbuild/win32-ia32': 0.25.5 + '@esbuild/win32-x64': 0.25.5 + + fsevents@2.3.3: + optional: true + + get-tsconfig@4.10.1: + dependencies: + resolve-pkg-maps: 1.0.0 + + openai@5.8.2: {} + + resolve-pkg-maps@1.0.0: {} + + tsx@4.20.3: + dependencies: + esbuild: 0.25.5 + get-tsconfig: 4.10.1 + optionalDependencies: + fsevents: 2.3.3 + + typescript@5.8.3: {} + + undici-types@6.21.0: {} diff --git a/examples/computer-example-ts/src/helpers.ts b/examples/computer-example-ts/src/helpers.ts new file mode 100644 index 00000000..adad2347 --- /dev/null +++ b/examples/computer-example-ts/src/helpers.ts @@ -0,0 +1,63 @@ +import type { Computer } from "@trycua/computer"; +import type OpenAI from "openai"; + +export async function executeAction( + computer: Computer, + action: OpenAI.Responses.ResponseComputerToolCall["action"], +) { + switch (action.type) { + case "click": { + const { x, y, button } = action; + console.log(`Executing click at (${x}, ${y}) with button '${button}'.`); + await computer.interface.moveCursor(x, y); + if (button === "right") await computer.interface.rightClick(); + else await computer.interface.leftClick(); + break; + } + case "type": + { + const { text } = action; + console.log(`Typing text: ${text}`); + await computer.interface.typeText(text); + } + break; + case "scroll": { + const { x: locX, y: locY, scroll_x, scroll_y } = action; + console.log( + `Scrolling at (${locX}, ${locY}) with offsets (scroll_x=${scroll_x}, scroll_y=${scroll_y}).`, + ); + await computer.interface.moveCursor(locX, locY); + await computer.interface.scroll(scroll_x, scroll_y); + break; + } + case "keypress": { + const { keys } = action; + for (const key of keys) { + console.log(`Pressing key: ${key}.`); + // Map common key names to CUA equivalents + if (key.toLowerCase() === "enter") { + await computer.interface.pressKey("return"); + } else if (key.toLowerCase() === "space") { + await computer.interface.pressKey("space"); + } else { + await computer.interface.pressKey(key); + } + } + break; + } + case "wait": { + console.log(`Waiting for 3 seconds.`); + await new Promise((resolve) => setTimeout(resolve, 3 * 1000)); + break; + } + case "screenshot": { + console.log("Taking screenshot."); + // This is handled automatically in the main loop, but we can take an extra one if requested + const screenshot = await computer.interface.screenshot(); + return screenshot; + } + default: + console.log(`Unrecognized action: ${action.type}`); + break; + } +} diff --git a/examples/computer-example-ts/src/index.ts b/examples/computer-example-ts/src/index.ts new file mode 100644 index 00000000..1077e088 --- /dev/null +++ b/examples/computer-example-ts/src/index.ts @@ -0,0 +1,104 @@ +import { Computer, OSType } from "@trycua/computer"; +import OpenAI from "openai"; +import { executeAction } from "./helpers"; + +import "dotenv/config"; + +const openai = new OpenAI({ apiKey: process.env.OPENAI_KEY }); + +const COMPUTER_USE_PROMPT = "Open firefox and go to trycua.com"; + +// Initialize the Computer Connection +const computer = new Computer({ + apiKey: process.env.CUA_KEY!, + name: process.env.CUA_CONTAINER_NAME!, + osType: OSType.LINUX, +}); + +await computer.run(); +// Take the initial screenshot +const screenshot = await computer.interface.screenshot(); +const screenshotBase64 = screenshot.toString("base64"); + +// Setup openai config for computer use +const computerUseConfig: OpenAI.Responses.ResponseCreateParamsNonStreaming = { + model: "computer-use-preview", + tools: [ + { + type: "computer_use_preview", + display_width: 1024, + display_height: 768, + environment: "linux", // we're using a linux vm + }, + ], + truncation: "auto", +}; + +// Send initial screenshot to the openai computer use model +let res = await openai.responses.create({ + ...computerUseConfig, + input: [ + { + role: "user", + content: [ + // what we want the ai to do + { type: "input_text", text: COMPUTER_USE_PROMPT }, + // current screenshot of the vm + { + type: "input_image", + image_url: `data:image/png;base64,${screenshotBase64}`, + detail: "auto", + }, + ], + }, + ], +}); + +// Loop until there are no more computer use actions. +while (true) { + const computerCalls = res.output.filter((o) => o.type === "computer_call"); + if (computerCalls.length < 1) { + console.log("No more computer calls. Loop complete."); + break; + } + // Get the first call + const call = computerCalls[0]; + const action = call.action; + console.log("Received action from OpenAI Responses API:", action); + let ackChecks: OpenAI.Responses.ResponseComputerToolCall.PendingSafetyCheck[] = + []; + if (call.pending_safety_checks.length > 0) { + console.log("Safety checks pending:", call.pending_safety_checks); + // In a real implementation, you would want to get user confirmation here + ackChecks = call.pending_safety_checks; + } + + // Execute the action in the container + await executeAction(computer, action); + // Wait for changes to process within the container (1sec) + await new Promise((resolve) => setTimeout(resolve, 1000)); + + // Capture new screenshot + const newScreenshot = await computer.interface.screenshot(); + const newScreenshotBase64 = newScreenshot.toString("base64"); + + // Screenshot back as computer_call_output + + res = await openai.responses.create({ + ...computerUseConfig, + previous_response_id: res.id, + input: [ + { + type: "computer_call_output", + call_id: call.call_id, + acknowledged_safety_checks: ackChecks, + output: { + type: "computer_screenshot", + image_url: `data:image/png;base64,${newScreenshotBase64}`, + }, + }, + ], + }); +} + +process.exit(); diff --git a/libs/typescript/examples/cua-cloud-openai/tsconfig.json b/examples/computer-example-ts/tsconfig.json similarity index 100% rename from libs/typescript/examples/cua-cloud-openai/tsconfig.json rename to examples/computer-example-ts/tsconfig.json diff --git a/libs/typescript/README.md b/libs/typescript/README.md index 7bf62a45..78fda2e7 100644 --- a/libs/typescript/README.md +++ b/libs/typescript/README.md @@ -2,8 +2,8 @@ This repository contains TypeScript implementations of the C/UA libraries: -- `@cua/core`: Core functionality including telemetry and logging -- `@cua/computer`: Computer interaction SDK for VM management and control +- `@trycua/core`: Core functionality including telemetry and logging +- `@trycua/computer`: Computer interaction SDK for VM management and control ## Project Structure @@ -48,10 +48,10 @@ Build specific packages: ```bash # Build core package -pnpm --filter @cua/core build +pnpm --filter @trycua/core build # Build computer package -pnpm --filter @cua/computer build +pnpm --filter @trycua/computer build ``` ### Running Tests @@ -66,10 +66,10 @@ Run tests for specific packages: ```bash # Test core package -pnpm --filter @cua/core test +pnpm --filter @trycua/core test # Test computer package -pnpm --filter @cua/computer test +pnpm --filter @trycua/computer test ``` ### Linting @@ -88,14 +88,14 @@ pnpm lint:fix:all ## Package Details -### @cua/core +### @trycua/core Core functionality for C/UA libraries including: - Telemetry with PostHog integration - Common utilities and types -### @cua/computer +### @trycua/computer Computer interaction SDK for managing and controlling virtual machines: diff --git a/libs/typescript/computer/README.md b/libs/typescript/computer/README.md index 98311be6..a9d42740 100644 --- a/libs/typescript/computer/README.md +++ b/libs/typescript/computer/README.md @@ -9,15 +9,15 @@ This library is a TypeScript port of the Python computer library, providing the ## Installation ```bash -npm install @cua/computer +npm install @trycua/computer # or -pnpm add @cua/computer +pnpm add @trycua/computer ``` ## Usage ```typescript -import { Computer } from '@cua/computer'; +import { Computer } from '@trycua/computer'; // Create a new computer instance const computer = new Computer({ diff --git a/libs/typescript/computer/package.json b/libs/typescript/computer/package.json index 9c36d415..4f71fb51 100644 --- a/libs/typescript/computer/package.json +++ b/libs/typescript/computer/package.json @@ -1,5 +1,5 @@ { - "name": "@cua/computer", + "name": "@trycua/computer", "version": "0.0.1", "packageManager": "pnpm@10.11.0", "description": "Typescript SDK for c/ua computer interaction", @@ -38,7 +38,7 @@ "prepublishOnly": "pnpm run build" }, "dependencies": { - "@cua/core": "link:../core", + "@trycua/core": "link:../core", "pino": "^9.7.0", "ws": "^8.18.0" }, diff --git a/libs/typescript/computer/src/computer/providers/base.ts b/libs/typescript/computer/src/computer/providers/base.ts index 16a2213e..ffd6da2d 100644 --- a/libs/typescript/computer/src/computer/providers/base.ts +++ b/libs/typescript/computer/src/computer/providers/base.ts @@ -1,117 +1,117 @@ -import os from 'node:os'; -import { Telemetry } from '@cua/core'; -import pino from 'pino'; -import type { OSType } from '../../types'; -import type { BaseComputerConfig, Display, VMProviderType } from '../types'; +import os from "node:os"; +import { Telemetry } from "@trycua/core"; +import pino from "pino"; +import type { OSType } from "../../types"; +import type { BaseComputerConfig, Display, VMProviderType } from "../types"; -const logger = pino({ name: 'computer.provider_base' }); +const logger = pino({ name: "computer.provider_base" }); /** * Base Computer class with shared functionality */ export abstract class BaseComputer { - protected name: string; - protected osType: OSType; - protected vmProvider?: VMProviderType; - protected telemetry: Telemetry; + protected name: string; + protected osType: OSType; + protected vmProvider?: VMProviderType; + protected telemetry: Telemetry; - constructor(config: BaseComputerConfig) { - this.name = config.name; - this.osType = config.osType; - this.telemetry = new Telemetry(); - this.telemetry.recordEvent('module_init', { - module: 'computer', - version: process.env.npm_package_version, - node_version: process.version, - }); + constructor(config: BaseComputerConfig) { + this.name = config.name; + this.osType = config.osType; + this.telemetry = new Telemetry(); + this.telemetry.recordEvent("module_init", { + module: "computer", + version: process.env.npm_package_version, + node_version: process.version, + }); - this.telemetry.recordEvent('computer_initialized', { - os: os.platform(), - os_version: os.version(), - node_version: process.version, - }); - } + this.telemetry.recordEvent("computer_initialized", { + os: os.platform(), + os_version: os.version(), + node_version: process.version, + }); + } - /** - * Get the name of the computer - */ - getName(): string { - return this.name; - } + /** + * Get the name of the computer + */ + getName(): string { + return this.name; + } - /** - * Get the OS type of the computer - */ - getOSType(): OSType { - return this.osType; - } + /** + * Get the OS type of the computer + */ + getOSType(): OSType { + return this.osType; + } - /** - * Get the VM provider type - */ - getVMProviderType(): VMProviderType | undefined { - return this.vmProvider; - } + /** + * Get the VM provider type + */ + getVMProviderType(): VMProviderType | undefined { + return this.vmProvider; + } - /** - * Shared method available to all computer types - */ - async disconnect(): Promise { - logger.info(`Disconnecting from ${this.name}`); - // Implementation would go here - } + /** + * Shared method available to all computer types + */ + async disconnect(): Promise { + logger.info(`Disconnecting from ${this.name}`); + // Implementation would go here + } - /** - * Parse display string into Display object - * @param display Display string in format "WIDTHxHEIGHT" - * @returns Display object - */ - public static parseDisplayString(display: string): Display { - const match = display.match(/^(\d+)x(\d+)$/); - if (!match) { - throw new Error( - `Invalid display format: ${display}. Expected format: WIDTHxHEIGHT` - ); - } + /** + * Parse display string into Display object + * @param display Display string in format "WIDTHxHEIGHT" + * @returns Display object + */ + public static parseDisplayString(display: string): Display { + const match = display.match(/^(\d+)x(\d+)$/); + if (!match) { + throw new Error( + `Invalid display format: ${display}. Expected format: WIDTHxHEIGHT`, + ); + } - return { - width: Number.parseInt(match[1], 10), - height: Number.parseInt(match[2], 10), - }; - } + return { + width: Number.parseInt(match[1], 10), + height: Number.parseInt(match[2], 10), + }; + } - /** - * Parse memory string to MB integer. - * - * Examples: - * "8GB" -> 8192 - * "1024MB" -> 1024 - * "512" -> 512 - * - * @param memoryStr - Memory string to parse - * @returns Memory value in MB - */ - public static parseMemoryString(memoryStr: string): number { - if (!memoryStr) { - return 0; - } + /** + * Parse memory string to MB integer. + * + * Examples: + * "8GB" -> 8192 + * "1024MB" -> 1024 + * "512" -> 512 + * + * @param memoryStr - Memory string to parse + * @returns Memory value in MB + */ + public static parseMemoryString(memoryStr: string): number { + if (!memoryStr) { + return 0; + } - // Convert to uppercase for case-insensitive matching - const upperStr = memoryStr.toUpperCase().trim(); + // Convert to uppercase for case-insensitive matching + const upperStr = memoryStr.toUpperCase().trim(); - // Extract numeric value and unit - const match = upperStr.match(/^(\d+(?:\.\d+)?)\s*(GB|MB)?$/); - if (!match) { - throw new Error(`Invalid memory format: ${memoryStr}`); - } + // Extract numeric value and unit + const match = upperStr.match(/^(\d+(?:\.\d+)?)\s*(GB|MB)?$/); + if (!match) { + throw new Error(`Invalid memory format: ${memoryStr}`); + } - const value = Number.parseFloat(match[1]); - const unit = match[2] || 'MB'; // Default to MB if no unit specified + const value = Number.parseFloat(match[1]); + const unit = match[2] || "MB"; // Default to MB if no unit specified - // Convert to MB - if (unit === 'GB') { - return Math.round(value * 1024); - } - return Math.round(value); - } + // Convert to MB + if (unit === "GB") { + return Math.round(value * 1024); + } + return Math.round(value); + } } diff --git a/libs/typescript/core/package.json b/libs/typescript/core/package.json index 36f18237..66cf7203 100644 --- a/libs/typescript/core/package.json +++ b/libs/typescript/core/package.json @@ -1,5 +1,5 @@ { - "name": "@cua/core", + "name": "@trycua/core", "version": "0.0.1", "packageManager": "pnpm@10.11.0", "description": "Typescript SDK for c/ua core", diff --git a/libs/typescript/examples/cua-cloud-openai/src/helpers.ts b/libs/typescript/examples/cua-cloud-openai/src/helpers.ts deleted file mode 100644 index 68062cd4..00000000 --- a/libs/typescript/examples/cua-cloud-openai/src/helpers.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Computer } from "@cua/computer"; -import OpenAI from "openai"; - -export async function executeAction( - computer: Computer, - action: OpenAI.Responses.ResponseComputerToolCall['action'] -) { - switch (action.type) { - case 'click': - const { x, y, button } = action; - console.log(`Executing click at (${x}, ${y}) with button '${button}'.`); - await computer.interface.moveCursor(x, y); - if (button === 'right') await computer.interface.rightClick(); - else await computer.interface.leftClick(); - break; - case 'type': - const { text } = action; - console.log(`Typing text: ${text}`); - await computer.interface.typeText(text); - break; - case 'scroll': - const { x: locX, y: locY, scroll_x, scroll_y } = action; - console.log( - `Scrolling at (${locX}, ${locY}) with offsets (scroll_x=${scroll_x}, scroll_y=${scroll_y}).` - ); - await computer.interface.moveCursor(locX, locY); - await computer.interface.scroll(scroll_x, scroll_y); - break; - case 'keypress': - const { keys } = action; - for (const key of keys) { - console.log(`Pressing key: ${key}.`); - // Map common key names to CUA equivalents - if (key.toLowerCase() === 'enter') { - await computer.interface.pressKey('return'); - } else if (key.toLowerCase() === 'space') { - await computer.interface.pressKey('space'); - } else { - await computer.interface.pressKey(key); - } - } - break; - case 'wait': - console.log(`Waiting for 3 seconds.`); - await new Promise((resolve) => setTimeout(resolve, 3 * 1000)); - break; - case 'screenshot': - console.log('Taking screenshot.'); - // This is handled automatically in the main loop, but we can take an extra one if requested - const screenshot = await computer.interface.screenshot(); - return screenshot; - default: - console.log(`Unrecognized action: ${action.type}`); - break; - } -} diff --git a/libs/typescript/examples/cua-cloud-openai/src/index.ts b/libs/typescript/examples/cua-cloud-openai/src/index.ts deleted file mode 100644 index eb8b0022..00000000 --- a/libs/typescript/examples/cua-cloud-openai/src/index.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { Computer, OSType } from '@cua/computer'; -import OpenAI from 'openai'; -import { executeAction } from './helpers'; - -import 'dotenv/config'; - -const openai = new OpenAI({ apiKey: process.env.OPENAI_KEY }); - -const COMPUTER_USE_PROMPT = 'Open firefox and go to trycua.com'; - -// Initialize the Computer Connection -const computer = new Computer({ - apiKey: process.env.CUA_KEY!, - name: process.env.CUA_CONTAINER_NAME!, - osType: OSType.LINUX, -}); - -await computer.run(); -// Take the initial screenshot -const screenshot = await computer.interface.screenshot(); -const screenshotBase64 = screenshot.toString('base64'); - -// Setup openai config for computer use -const computerUseConfig: OpenAI.Responses.ResponseCreateParamsNonStreaming = { - model: 'computer-use-preview', - tools: [ - { - type: 'computer_use_preview', - display_width: 1024, - display_height: 768, - environment: 'linux', // we're using a linux vm - }, - ], - truncation: 'auto', -}; - -// Send initial screenshot to the openai computer use model -let res = await openai.responses.create({ - ...computerUseConfig, - input: [ - { - role: 'user', - content: [ - // what we want the ai to do - { type: 'input_text', text: COMPUTER_USE_PROMPT }, - // current screenshot of the vm - { - type: 'input_image', - image_url: `data:image/png;base64,${screenshotBase64}`, - detail: 'auto', - }, - ], - }, - ], -}); - -// Loop until there are no more computer use actions. -while (true) { - const computerCalls = res.output.filter((o) => o.type === 'computer_call'); - if (computerCalls.length < 1) { - console.log('No more computer calls. Loop complete.'); - break; - } - // Get the first call - const call = computerCalls[0]; - const action = call.action; - console.log('Received action from OpenAI Responses API:', action); - let ackChecks: OpenAI.Responses.ResponseComputerToolCall.PendingSafetyCheck[] = - []; - if (call.pending_safety_checks.length > 0) { - console.log('Safety checks pending:', call.pending_safety_checks); - // In a real implementation, you would want to get user confirmation here - ackChecks = call.pending_safety_checks; - } - - // Execute the action in the container - await executeAction(computer, action); - // Wait for changes to process within the container (1sec) - await new Promise((resolve) => setTimeout(resolve, 1000)); - - // Capture new screenshot - const newScreenshot = await computer.interface.screenshot(); - const newScreenshotBase64 = newScreenshot.toString('base64'); - - // Screenshot back as computer_call_output - - res = await openai.responses.create({ - ...computerUseConfig, - previous_response_id: res.id, - input: [ - { - type: 'computer_call_output', - call_id: call.call_id, - acknowledged_safety_checks: ackChecks, - output: { - type: 'computer_screenshot', - image_url: `data:image/png;base64,${newScreenshotBase64}`, - }, - }, - ], - }); -} - -process.exit(); diff --git a/libs/typescript/package.json b/libs/typescript/package.json index 134f8600..0e0d6ce1 100644 --- a/libs/typescript/package.json +++ b/libs/typescript/package.json @@ -8,11 +8,11 @@ "scripts": { "lint": "biome check", "lint:fix": "biome check --fix", - "build:core": "pnpm --filter @cua/core build", - "build:computer": "pnpm --filter @cua/computer build", + "build:core": "pnpm --filter @trycua/core build", + "build:computer": "pnpm --filter @trycua/computer build", "build": "pnpm build:core && pnpm build:computer", - "test:core": "pnpm --filter @cua/core test", - "test:computer": "pnpm --filter @cua/computer test", + "test:core": "pnpm --filter @trycua/core test", + "test:computer": "pnpm --filter @trycua/computer test", "test": "pnpm -r test", "typecheck": "pnpm -r typecheck" }, diff --git a/libs/typescript/pnpm-lock.yaml b/libs/typescript/pnpm-lock.yaml index 69ea7745..0050b9b6 100644 --- a/libs/typescript/pnpm-lock.yaml +++ b/libs/typescript/pnpm-lock.yaml @@ -14,7 +14,7 @@ importers: computer: dependencies: - '@cua/core': + '@trycua/core': specifier: link:../core version: link:../core pino: @@ -22,14 +22,14 @@ importers: version: 9.7.0 ws: specifier: ^8.18.0 - version: 8.18.2 + version: 8.18.3 devDependencies: '@biomejs/biome': specifier: ^1.9.4 version: 1.9.4 '@types/node': specifier: ^22.15.17 - version: 22.15.33 + version: 22.15.34 '@types/ws': specifier: ^8.18.1 version: 8.18.1 @@ -50,7 +50,7 @@ importers: version: 5.8.3 vitest: specifier: ^3.1.3 - version: 3.2.4(@types/node@22.15.33)(happy-dom@17.6.3)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + version: 3.2.4(@types/node@22.15.34)(happy-dom@17.6.3)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) core: dependencies: @@ -72,7 +72,7 @@ importers: version: 1.9.4 '@types/node': specifier: ^22.15.17 - version: 22.15.33 + version: 22.15.34 '@types/ws': specifier: ^8.18.1 version: 8.18.1 @@ -93,45 +93,7 @@ importers: version: 5.8.3 vitest: specifier: ^3.1.3 - version: 3.2.4(@types/node@22.15.33)(happy-dom@17.6.3)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) - - examples/byo-operator-ts: - dependencies: - '@cua/computer': - specifier: link:../../computer - version: link:../../computer - openai: - specifier: ^5.7.0 - version: 5.7.0(ws@8.18.2) - devDependencies: - tsx: - specifier: ^4.20.3 - version: 4.20.3 - typescript: - specifier: ^5.8.3 - version: 5.8.3 - - examples/cua-openai: - dependencies: - '@cua/computer': - specifier: link:../../computer - version: link:../../computer - dotenv: - specifier: ^16.5.0 - version: 16.5.0 - openai: - specifier: ^5.7.0 - version: 5.7.0(ws@8.18.2) - devDependencies: - '@types/node': - specifier: ^22.15.33 - version: 22.15.33 - tsx: - specifier: ^4.20.3 - version: 4.20.3 - typescript: - specifier: ^5.8.3 - version: 5.8.3 + version: 3.2.4(@types/node@22.15.34)(happy-dom@17.6.3)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) packages: @@ -147,13 +109,13 @@ packages: resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} - '@babel/parser@7.27.5': - resolution: {integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==} + '@babel/parser@7.27.7': + resolution: {integrity: sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/types@7.27.6': - resolution: {integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==} + '@babel/types@7.27.7': + resolution: {integrity: sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==} engines: {node: '>=6.9.0'} '@biomejs/biome@1.9.4': @@ -368,23 +330,18 @@ packages: cpu: [x64] os: [win32] - '@jridgewell/gen-mapping@0.3.8': - resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} - engines: {node: '>=6.0.0'} + '@jridgewell/gen-mapping@0.3.10': + resolution: {integrity: sha512-HM2F4B9N4cA0RH2KQiIZOHAZqtP4xGS4IZ+SFe1SIbO4dyjf9MTY2Bo3vHYnm0hglWfXqBrzUBSa+cJfl3Xvrg==} '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} + '@jridgewell/sourcemap-codec@1.5.2': + resolution: {integrity: sha512-gKYheCylLIedI+CSZoDtGkFV9YEBxRRVcfCH7OfAqh4TyUyRjEE6WVE/aXDXX0p8BIe/QgLcaAoI0220KRRFgg==} - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.27': + resolution: {integrity: sha512-VO95AxtSFMelbg3ouljAYnfvTEwSWVt/2YLf+U5Ejd8iT5mXE2Sa/1LGyvySMne2CGsepGLI7KpF3EzE3Aq9Mg==} '@napi-rs/wasm-runtime@0.2.11': resolution: {integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==} @@ -459,103 +416,103 @@ packages: '@rolldown/pluginutils@1.0.0-beta.9': resolution: {integrity: sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==} - '@rollup/rollup-android-arm-eabi@4.44.0': - resolution: {integrity: sha512-xEiEE5oDW6tK4jXCAyliuntGR+amEMO7HLtdSshVuhFnKTYoeYMyXQK7pLouAJJj5KHdwdn87bfHAR2nSdNAUA==} + '@rollup/rollup-android-arm-eabi@4.44.1': + resolution: {integrity: sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.44.0': - resolution: {integrity: sha512-uNSk/TgvMbskcHxXYHzqwiyBlJ/lGcv8DaUfcnNwict8ba9GTTNxfn3/FAoFZYgkaXXAdrAA+SLyKplyi349Jw==} + '@rollup/rollup-android-arm64@4.44.1': + resolution: {integrity: sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.44.0': - resolution: {integrity: sha512-VGF3wy0Eq1gcEIkSCr8Ke03CWT+Pm2yveKLaDvq51pPpZza3JX/ClxXOCmTYYq3us5MvEuNRTaeyFThCKRQhOA==} + '@rollup/rollup-darwin-arm64@4.44.1': + resolution: {integrity: sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.44.0': - resolution: {integrity: sha512-fBkyrDhwquRvrTxSGH/qqt3/T0w5Rg0L7ZIDypvBPc1/gzjJle6acCpZ36blwuwcKD/u6oCE/sRWlUAcxLWQbQ==} + '@rollup/rollup-darwin-x64@4.44.1': + resolution: {integrity: sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.44.0': - resolution: {integrity: sha512-u5AZzdQJYJXByB8giQ+r4VyfZP+walV+xHWdaFx/1VxsOn6eWJhK2Vl2eElvDJFKQBo/hcYIBg/jaKS8ZmKeNQ==} + '@rollup/rollup-freebsd-arm64@4.44.1': + resolution: {integrity: sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.44.0': - resolution: {integrity: sha512-qC0kS48c/s3EtdArkimctY7h3nHicQeEUdjJzYVJYR3ct3kWSafmn6jkNCA8InbUdge6PVx6keqjk5lVGJf99g==} + '@rollup/rollup-freebsd-x64@4.44.1': + resolution: {integrity: sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.44.0': - resolution: {integrity: sha512-x+e/Z9H0RAWckn4V2OZZl6EmV0L2diuX3QB0uM1r6BvhUIv6xBPL5mrAX2E3e8N8rEHVPwFfz/ETUbV4oW9+lQ==} + '@rollup/rollup-linux-arm-gnueabihf@4.44.1': + resolution: {integrity: sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.44.0': - resolution: {integrity: sha512-1exwiBFf4PU/8HvI8s80icyCcnAIB86MCBdst51fwFmH5dyeoWVPVgmQPcKrMtBQ0W5pAs7jBCWuRXgEpRzSCg==} + '@rollup/rollup-linux-arm-musleabihf@4.44.1': + resolution: {integrity: sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.44.0': - resolution: {integrity: sha512-ZTR2mxBHb4tK4wGf9b8SYg0Y6KQPjGpR4UWwTFdnmjB4qRtoATZ5dWn3KsDwGa5Z2ZBOE7K52L36J9LueKBdOQ==} + '@rollup/rollup-linux-arm64-gnu@4.44.1': + resolution: {integrity: sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.44.0': - resolution: {integrity: sha512-GFWfAhVhWGd4r6UxmnKRTBwP1qmModHtd5gkraeW2G490BpFOZkFtem8yuX2NyafIP/mGpRJgTJ2PwohQkUY/Q==} + '@rollup/rollup-linux-arm64-musl@4.44.1': + resolution: {integrity: sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.44.0': - resolution: {integrity: sha512-xw+FTGcov/ejdusVOqKgMGW3c4+AgqrfvzWEVXcNP6zq2ue+lsYUgJ+5Rtn/OTJf7e2CbgTFvzLW2j0YAtj0Gg==} + '@rollup/rollup-linux-loongarch64-gnu@4.44.1': + resolution: {integrity: sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.44.0': - resolution: {integrity: sha512-bKGibTr9IdF0zr21kMvkZT4K6NV+jjRnBoVMt2uNMG0BYWm3qOVmYnXKzx7UhwrviKnmK46IKMByMgvpdQlyJQ==} + '@rollup/rollup-linux-powerpc64le-gnu@4.44.1': + resolution: {integrity: sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.44.0': - resolution: {integrity: sha512-vV3cL48U5kDaKZtXrti12YRa7TyxgKAIDoYdqSIOMOFBXqFj2XbChHAtXquEn2+n78ciFgr4KIqEbydEGPxXgA==} + '@rollup/rollup-linux-riscv64-gnu@4.44.1': + resolution: {integrity: sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.44.0': - resolution: {integrity: sha512-TDKO8KlHJuvTEdfw5YYFBjhFts2TR0VpZsnLLSYmB7AaohJhM8ctDSdDnUGq77hUh4m/djRafw+9zQpkOanE2Q==} + '@rollup/rollup-linux-riscv64-musl@4.44.1': + resolution: {integrity: sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.44.0': - resolution: {integrity: sha512-8541GEyktXaw4lvnGp9m84KENcxInhAt6vPWJ9RodsB/iGjHoMB2Pp5MVBCiKIRxrxzJhGCxmNzdu+oDQ7kwRA==} + '@rollup/rollup-linux-s390x-gnu@4.44.1': + resolution: {integrity: sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.44.0': - resolution: {integrity: sha512-iUVJc3c0o8l9Sa/qlDL2Z9UP92UZZW1+EmQ4xfjTc1akr0iUFZNfxrXJ/R1T90h/ILm9iXEY6+iPrmYB3pXKjw==} + '@rollup/rollup-linux-x64-gnu@4.44.1': + resolution: {integrity: sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.44.0': - resolution: {integrity: sha512-PQUobbhLTQT5yz/SPg116VJBgz+XOtXt8D1ck+sfJJhuEsMj2jSej5yTdp8CvWBSceu+WW+ibVL6dm0ptG5fcA==} + '@rollup/rollup-linux-x64-musl@4.44.1': + resolution: {integrity: sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.44.0': - resolution: {integrity: sha512-M0CpcHf8TWn+4oTxJfh7LQuTuaYeXGbk0eageVjQCKzYLsajWS/lFC94qlRqOlyC2KvRT90ZrfXULYmukeIy7w==} + '@rollup/rollup-win32-arm64-msvc@4.44.1': + resolution: {integrity: sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.44.0': - resolution: {integrity: sha512-3XJ0NQtMAXTWFW8FqZKcw3gOQwBtVWP/u8TpHP3CRPXD7Pd6s8lLdH3sHWh8vqKCyyiI8xW5ltJScQmBU9j7WA==} + '@rollup/rollup-win32-ia32-msvc@4.44.1': + resolution: {integrity: sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.44.0': - resolution: {integrity: sha512-Q2Mgwt+D8hd5FIPUuPDsvPR7Bguza6yTkJxspDGkZj7tBRn2y4KSWYuIXpftFSjBra76TbKerCV7rgFPQrn+wQ==} + '@rollup/rollup-win32-x64-msvc@4.44.1': + resolution: {integrity: sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug==} cpu: [x64] os: [win32] @@ -571,8 +528,8 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/node@22.15.33': - resolution: {integrity: sha512-wzoocdnnpSxZ+6CjW4ADCK1jVmd1S/J3ArNWfn8FDDQtRm8dkDg7TA+mvek2wNrfCgwuZxqEOiB9B1XCJ6+dbw==} + '@types/node@22.15.34': + resolution: {integrity: sha512-8Y6E5WUupYy1Dd0II32BsWAx5MWdcnRd8L84Oys3veg1YrYtNtzgO4CFhiBg6MDSjk7Ay36HYOnU7/tuOzIzcw==} '@types/uuid@10.0.0': resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} @@ -693,8 +650,8 @@ packages: resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} engines: {node: '>=0.3.1'} - dotenv@16.5.0: - resolution: {integrity: sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==} + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} dts-resolver@2.1.1: @@ -807,18 +764,6 @@ packages: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} - openai@5.7.0: - resolution: {integrity: sha512-zXWawZl6J/P5Wz57/nKzVT3kJQZvogfuyuNVCdEp4/XU2UNrjL7SsuNpWAyLZbo6HVymwmnfno9toVzBhelygA==} - hasBin: true - peerDependencies: - ws: ^8.18.0 - zod: ^3.23.8 - peerDependenciesMeta: - ws: - optional: true - zod: - optional: true - package-manager-detector@1.3.0: resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} @@ -849,8 +794,8 @@ packages: resolution: {integrity: sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==} hasBin: true - pkg-types@2.1.0: - resolution: {integrity: sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==} + pkg-types@2.1.1: + resolution: {integrity: sha512-eY0QFb6eSwc9+0d/5D2lFFUq+A3n3QNGSy/X2Nvp+6MfzGw2u6EbA7S80actgjY1lkvvI0pqB+a4hioMh443Ew==} postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} @@ -883,8 +828,8 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - rolldown-plugin-dts@0.13.12: - resolution: {integrity: sha512-4QdQQfMOWNDLhmvoG3USAUpCm75dgwWk3IrK6SV9Fw2U5uwcSE7oQTqEcmsUGEBsNxZ58+gtM1oX38MMf0g5PA==} + rolldown-plugin-dts@0.13.13: + resolution: {integrity: sha512-Nchx9nQoa4IpfQ/BJzodKMvtJ3H3dT322siAJSp3uvQJ+Pi1qgEjOp7hSQwGSQRhaC5gC+9hparbWEH5oiAL9Q==} engines: {node: '>=20.18.0'} peerDependencies: '@typescript/native-preview': '>=7.0.0-dev.20250601.1' @@ -908,8 +853,8 @@ packages: '@oxc-project/runtime': optional: true - rollup@4.44.0: - resolution: {integrity: sha512-qHcdEzLCiktQIfwBq420pn2dP+30uzqYxv9ETm91wdt2R9AFcWfjNAmje4NWlnCIQ5RMTzVf0ZyisOKqHR6RwA==} + rollup@4.44.1: + resolution: {integrity: sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -1101,8 +1046,8 @@ packages: engines: {node: '>=8'} hasBin: true - ws@8.18.2: - resolution: {integrity: sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==} + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -1122,21 +1067,21 @@ snapshots: '@babel/generator@7.27.5': dependencies: - '@babel/parser': 7.27.5 - '@babel/types': 7.27.6 - '@jridgewell/gen-mapping': 0.3.8 - '@jridgewell/trace-mapping': 0.3.25 + '@babel/parser': 7.27.7 + '@babel/types': 7.27.7 + '@jridgewell/gen-mapping': 0.3.10 + '@jridgewell/trace-mapping': 0.3.27 jsesc: 3.1.0 '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.27.1': {} - '@babel/parser@7.27.5': + '@babel/parser@7.27.7': dependencies: - '@babel/types': 7.27.6 + '@babel/types': 7.27.7 - '@babel/types@7.27.6': + '@babel/types@7.27.7': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 @@ -1267,22 +1212,19 @@ snapshots: '@esbuild/win32-x64@0.25.5': optional: true - '@jridgewell/gen-mapping@0.3.8': + '@jridgewell/gen-mapping@0.3.10': dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/sourcemap-codec': 1.5.2 + '@jridgewell/trace-mapping': 0.3.27 '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/set-array@1.2.1': {} + '@jridgewell/sourcemap-codec@1.5.2': {} - '@jridgewell/sourcemap-codec@1.5.0': {} - - '@jridgewell/trace-mapping@0.3.25': + '@jridgewell/trace-mapping@0.3.27': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.2 '@napi-rs/wasm-runtime@0.2.11': dependencies: @@ -1337,64 +1279,64 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.9': {} - '@rollup/rollup-android-arm-eabi@4.44.0': + '@rollup/rollup-android-arm-eabi@4.44.1': optional: true - '@rollup/rollup-android-arm64@4.44.0': + '@rollup/rollup-android-arm64@4.44.1': optional: true - '@rollup/rollup-darwin-arm64@4.44.0': + '@rollup/rollup-darwin-arm64@4.44.1': optional: true - '@rollup/rollup-darwin-x64@4.44.0': + '@rollup/rollup-darwin-x64@4.44.1': optional: true - '@rollup/rollup-freebsd-arm64@4.44.0': + '@rollup/rollup-freebsd-arm64@4.44.1': optional: true - '@rollup/rollup-freebsd-x64@4.44.0': + '@rollup/rollup-freebsd-x64@4.44.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.44.0': + '@rollup/rollup-linux-arm-gnueabihf@4.44.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.44.0': + '@rollup/rollup-linux-arm-musleabihf@4.44.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.44.0': + '@rollup/rollup-linux-arm64-gnu@4.44.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.44.0': + '@rollup/rollup-linux-arm64-musl@4.44.1': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.44.0': + '@rollup/rollup-linux-loongarch64-gnu@4.44.1': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.44.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.44.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.44.0': + '@rollup/rollup-linux-riscv64-gnu@4.44.1': optional: true - '@rollup/rollup-linux-riscv64-musl@4.44.0': + '@rollup/rollup-linux-riscv64-musl@4.44.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.44.0': + '@rollup/rollup-linux-s390x-gnu@4.44.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.44.0': + '@rollup/rollup-linux-x64-gnu@4.44.1': optional: true - '@rollup/rollup-linux-x64-musl@4.44.0': + '@rollup/rollup-linux-x64-musl@4.44.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.44.0': + '@rollup/rollup-win32-arm64-msvc@4.44.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.44.0': + '@rollup/rollup-win32-ia32-msvc@4.44.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.44.0': + '@rollup/rollup-win32-x64-msvc@4.44.1': optional: true '@tybys/wasm-util@0.9.0': @@ -1410,7 +1352,7 @@ snapshots: '@types/estree@1.0.8': {} - '@types/node@22.15.33': + '@types/node@22.15.34': dependencies: undici-types: 6.21.0 @@ -1418,7 +1360,7 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 22.15.33 + '@types/node': 22.15.34 '@vitest/expect@3.2.4': dependencies: @@ -1428,13 +1370,13 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.0.0(@types/node@22.15.33)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0))': + '@vitest/mocker@3.2.4(vite@7.0.0(@types/node@22.15.34)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - vite: 7.0.0(@types/node@22.15.33)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + vite: 7.0.0(@types/node@22.15.34)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) '@vitest/pretty-format@3.2.4': dependencies: @@ -1470,7 +1412,7 @@ snapshots: ast-kit@2.1.0: dependencies: - '@babel/parser': 7.27.5 + '@babel/parser': 7.27.7 pathe: 2.0.3 atomic-sleep@1.0.0: {} @@ -1498,14 +1440,14 @@ snapshots: chokidar: 4.0.3 confbox: 0.2.2 defu: 6.1.4 - dotenv: 16.5.0 + dotenv: 16.6.1 exsolve: 1.0.7 giget: 2.0.0 jiti: 2.4.2 ohash: 2.0.11 pathe: 2.0.3 perfect-debounce: 1.0.0 - pkg-types: 2.1.0 + pkg-types: 2.1.1 rc9: 2.1.2 cac@6.7.14: {} @@ -1544,7 +1486,7 @@ snapshots: diff@8.0.2: {} - dotenv@16.5.0: {} + dotenv@16.6.1: {} dts-resolver@2.1.1: {} @@ -1631,7 +1573,7 @@ snapshots: magic-string@0.30.17: dependencies: - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.2 ms@2.1.3: {} @@ -1644,17 +1586,13 @@ snapshots: citty: 0.1.6 consola: 3.4.2 pathe: 2.0.3 - pkg-types: 2.1.0 + pkg-types: 2.1.1 tinyexec: 0.3.2 ohash@2.0.11: {} on-exit-leak-free@2.1.2: {} - openai@5.7.0(ws@8.18.2): - optionalDependencies: - ws: 8.18.2 - package-manager-detector@1.3.0: {} pathe@2.0.3: {} @@ -1687,7 +1625,7 @@ snapshots: sonic-boom: 4.2.0 thread-stream: 3.1.0 - pkg-types@2.1.0: + pkg-types@2.1.1: dependencies: confbox: 0.2.2 exsolve: 1.0.7 @@ -1718,11 +1656,11 @@ snapshots: resolve-pkg-maps@1.0.0: {} - rolldown-plugin-dts@0.13.12(rolldown@1.0.0-beta.9)(typescript@5.8.3): + rolldown-plugin-dts@0.13.13(rolldown@1.0.0-beta.9)(typescript@5.8.3): dependencies: '@babel/generator': 7.27.5 - '@babel/parser': 7.27.5 - '@babel/types': 7.27.6 + '@babel/parser': 7.27.7 + '@babel/types': 7.27.7 ast-kit: 2.1.0 birpc: 2.4.0 debug: 4.4.1 @@ -1754,30 +1692,30 @@ snapshots: '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.9 '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.9 - rollup@4.44.0: + rollup@4.44.1: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.44.0 - '@rollup/rollup-android-arm64': 4.44.0 - '@rollup/rollup-darwin-arm64': 4.44.0 - '@rollup/rollup-darwin-x64': 4.44.0 - '@rollup/rollup-freebsd-arm64': 4.44.0 - '@rollup/rollup-freebsd-x64': 4.44.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.44.0 - '@rollup/rollup-linux-arm-musleabihf': 4.44.0 - '@rollup/rollup-linux-arm64-gnu': 4.44.0 - '@rollup/rollup-linux-arm64-musl': 4.44.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.44.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.44.0 - '@rollup/rollup-linux-riscv64-gnu': 4.44.0 - '@rollup/rollup-linux-riscv64-musl': 4.44.0 - '@rollup/rollup-linux-s390x-gnu': 4.44.0 - '@rollup/rollup-linux-x64-gnu': 4.44.0 - '@rollup/rollup-linux-x64-musl': 4.44.0 - '@rollup/rollup-win32-arm64-msvc': 4.44.0 - '@rollup/rollup-win32-ia32-msvc': 4.44.0 - '@rollup/rollup-win32-x64-msvc': 4.44.0 + '@rollup/rollup-android-arm-eabi': 4.44.1 + '@rollup/rollup-android-arm64': 4.44.1 + '@rollup/rollup-darwin-arm64': 4.44.1 + '@rollup/rollup-darwin-x64': 4.44.1 + '@rollup/rollup-freebsd-arm64': 4.44.1 + '@rollup/rollup-freebsd-x64': 4.44.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.44.1 + '@rollup/rollup-linux-arm-musleabihf': 4.44.1 + '@rollup/rollup-linux-arm64-gnu': 4.44.1 + '@rollup/rollup-linux-arm64-musl': 4.44.1 + '@rollup/rollup-linux-loongarch64-gnu': 4.44.1 + '@rollup/rollup-linux-powerpc64le-gnu': 4.44.1 + '@rollup/rollup-linux-riscv64-gnu': 4.44.1 + '@rollup/rollup-linux-riscv64-musl': 4.44.1 + '@rollup/rollup-linux-s390x-gnu': 4.44.1 + '@rollup/rollup-linux-x64-gnu': 4.44.1 + '@rollup/rollup-linux-x64-musl': 4.44.1 + '@rollup/rollup-win32-arm64-msvc': 4.44.1 + '@rollup/rollup-win32-ia32-msvc': 4.44.1 + '@rollup/rollup-win32-x64-msvc': 4.44.1 fsevents: 2.3.3 safe-stable-stringify@2.5.0: {} @@ -1833,7 +1771,7 @@ snapshots: empathic: 1.1.0 hookable: 5.5.3 rolldown: 1.0.0-beta.9 - rolldown-plugin-dts: 0.13.12(rolldown@1.0.0-beta.9)(typescript@5.8.3) + rolldown-plugin-dts: 0.13.13(rolldown@1.0.0-beta.9)(typescript@5.8.3) semver: 7.7.2 tinyexec: 1.0.1 tinyglobby: 0.2.14 @@ -1870,13 +1808,13 @@ snapshots: uuid@11.1.0: {} - vite-node@3.2.4(@types/node@22.15.33)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0): + vite-node@3.2.4(@types/node@22.15.34)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0): dependencies: cac: 6.7.14 debug: 4.4.1 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.0.0(@types/node@22.15.33)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + vite: 7.0.0(@types/node@22.15.34)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) transitivePeerDependencies: - '@types/node' - jiti @@ -1891,26 +1829,26 @@ snapshots: - tsx - yaml - vite@7.0.0(@types/node@22.15.33)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0): + vite@7.0.0(@types/node@22.15.34)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0): dependencies: esbuild: 0.25.5 fdir: 6.4.6(picomatch@4.0.2) picomatch: 4.0.2 postcss: 8.5.6 - rollup: 4.44.0 + rollup: 4.44.1 tinyglobby: 0.2.14 optionalDependencies: - '@types/node': 22.15.33 + '@types/node': 22.15.34 fsevents: 2.3.3 jiti: 2.4.2 tsx: 4.20.3 yaml: 2.8.0 - vitest@3.2.4(@types/node@22.15.33)(happy-dom@17.6.3)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0): + vitest@3.2.4(@types/node@22.15.34)(happy-dom@17.6.3)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.0.0(@types/node@22.15.33)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0)) + '@vitest/mocker': 3.2.4(vite@7.0.0(@types/node@22.15.34)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -1928,11 +1866,11 @@ snapshots: tinyglobby: 0.2.14 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.0.0(@types/node@22.15.33)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) - vite-node: 3.2.4(@types/node@22.15.33)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + vite: 7.0.0(@types/node@22.15.34)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) + vite-node: 3.2.4(@types/node@22.15.34)(jiti@2.4.2)(tsx@4.20.3)(yaml@2.8.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 22.15.33 + '@types/node': 22.15.34 happy-dom: 17.6.3 transitivePeerDependencies: - jiti @@ -1957,6 +1895,6 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 - ws@8.18.2: {} + ws@8.18.3: {} yaml@2.8.0: {} From 1d5a2a7a2df8a0eb0bd6aed28d5d9cd2a60ebada Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Mon, 30 Jun 2025 10:49:04 -0700 Subject: [PATCH 114/141] Enable provenance --- .github/workflows/npm-publish-computer.yml | 9 ++++++--- .github/workflows/npm-publish-core.yml | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/.github/workflows/npm-publish-computer.yml b/.github/workflows/npm-publish-computer.yml index 2f67439b..328378d7 100644 --- a/.github/workflows/npm-publish-computer.yml +++ b/.github/workflows/npm-publish-computer.yml @@ -7,14 +7,17 @@ on: jobs: publish: + permissions: + id-token: write + contents: read runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Use Node.js LTS + - name: Use Node.js 24.x uses: actions/setup-node@v4 with: - node-version: lts/* + node-version: "24.x" registry-url: "https://registry.npmjs.org" - name: Install dependencies @@ -27,6 +30,6 @@ jobs: - name: Publish to npm working-directory: ./libs/typescript/computer - run: npm publish --access public + run: npm publish --provenance --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/npm-publish-core.yml b/.github/workflows/npm-publish-core.yml index e7bd996b..dc06628d 100644 --- a/.github/workflows/npm-publish-core.yml +++ b/.github/workflows/npm-publish-core.yml @@ -7,14 +7,17 @@ on: jobs: publish: + permissions: + id-token: write + contents: read runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Use Node.js LTS + - name: Use Node.js 24.x uses: actions/setup-node@v4 with: - node-version: lts/* + node-version: "24.x" registry-url: "https://registry.npmjs.org" - name: Install dependencies @@ -27,6 +30,6 @@ jobs: - name: Publish to npm working-directory: ./libs/typescript/core - run: npm publish --access public + run: npm publish --provenance --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From f4b6b5942bc67e3331da5c442f6b8aa894bd12a1 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Mon, 30 Jun 2025 11:41:26 -0700 Subject: [PATCH 115/141] Add homepage to ts libs --- libs/typescript/computer/package.json | 2 +- libs/typescript/core/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/typescript/computer/package.json b/libs/typescript/computer/package.json index 4f71fb51..797ecbc3 100644 --- a/libs/typescript/computer/package.json +++ b/libs/typescript/computer/package.json @@ -5,7 +5,7 @@ "description": "Typescript SDK for c/ua computer interaction", "type": "module", "license": "MIT", - "homepage": "", + "homepage": "https://github.com/trycua/cua/tree/feature/computer/typescript/libs/typescript/computer", "bugs": { "url": "https://github.com/trycua/cua/issues" }, diff --git a/libs/typescript/core/package.json b/libs/typescript/core/package.json index 66cf7203..2a03920c 100644 --- a/libs/typescript/core/package.json +++ b/libs/typescript/core/package.json @@ -5,7 +5,7 @@ "description": "Typescript SDK for c/ua core", "type": "module", "license": "MIT", - "homepage": "", + "homepage": "https://github.com/trycua/cua/tree/feature/computer/typescript/libs/typescript/computer", "bugs": { "url": "https://github.com/trycua/cua/issues" }, From 3e6f61f54f15d07d48ee4f456a0a8f014d75bdaa Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 1 Jul 2025 10:57:55 -0400 Subject: [PATCH 116/141] Refactored reused code in python interface --- libs/python/agent/pyproject.toml | 2 +- .../computer/computer/interface/generic.py | 771 ++++++++++++++++++ .../computer/computer/interface/linux.py | 735 +---------------- .../computer/computer/interface/macos.py | 740 +---------------- .../computer/computer/interface/windows.py | 734 +---------------- 5 files changed, 785 insertions(+), 2197 deletions(-) create mode 100644 libs/python/computer/computer/interface/generic.py diff --git a/libs/python/agent/pyproject.toml b/libs/python/agent/pyproject.toml index fd25a0e8..7f6af835 100644 --- a/libs/python/agent/pyproject.toml +++ b/libs/python/agent/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "pdm.backend" [project] name = "cua-agent" -version = "0.2.0" +version = "0.3.0" description = "CUA (Computer Use) Agent for AI-driven computer interaction" readme = "README.md" authors = [ diff --git a/libs/python/computer/computer/interface/generic.py b/libs/python/computer/computer/interface/generic.py new file mode 100644 index 00000000..46c949c3 --- /dev/null +++ b/libs/python/computer/computer/interface/generic.py @@ -0,0 +1,771 @@ +import asyncio +import json +import time +from typing import Any, Dict, List, Optional, Tuple +from PIL import Image + +import websockets + +from ..logger import Logger, LogLevel +from .base import BaseComputerInterface +from ..utils import decode_base64_image, encode_base64_image, bytes_to_image, draw_box, resize_image +from .models import Key, KeyType, MouseButton, CommandResult + + +class GenericComputerInterface(BaseComputerInterface): + """Generic interface with common functionality for all supported platforms (Windows, Linux, macOS).""" + + def __init__(self, ip_address: str, username: str = "lume", password: str = "lume", api_key: Optional[str] = None, vm_name: Optional[str] = None, logger_name: str = "computer.interface.generic"): + super().__init__(ip_address, username, password, api_key, vm_name) + self._ws = None + self._reconnect_task = None + self._closed = False + self._last_ping = 0 + self._ping_interval = 5 # Send ping every 5 seconds + self._ping_timeout = 120 # Wait 120 seconds for pong response + self._reconnect_delay = 1 # Start with 1 second delay + self._max_reconnect_delay = 30 # Maximum delay between reconnection attempts + self._log_connection_attempts = True # Flag to control connection attempt logging + self._authenticated = False # Track authentication status + self._command_lock = asyncio.Lock() # Lock to ensure only one command at a time + + # Set logger name for the interface + self.logger = Logger(logger_name, LogLevel.NORMAL) + + @property + def ws_uri(self) -> str: + """Get the WebSocket URI using the current IP address. + + Returns: + WebSocket URI for the Computer API Server + """ + protocol = "wss" if self.api_key else "ws" + port = "8443" if self.api_key else "8000" + return f"{protocol}://{self.ip_address}:{port}/ws" + + # Mouse actions + async def mouse_down(self, x: Optional[int] = None, y: Optional[int] = None, button: str = "left") -> None: + await self._send_command("mouse_down", {"x": x, "y": y, "button": button}) + + async def mouse_up(self, x: Optional[int] = None, y: Optional[int] = None, button: str = "left") -> None: + await self._send_command("mouse_up", {"x": x, "y": y, "button": button}) + + async def left_click(self, x: Optional[int] = None, y: Optional[int] = None) -> None: + await self._send_command("left_click", {"x": x, "y": y}) + + async def right_click(self, x: Optional[int] = None, y: Optional[int] = None) -> None: + await self._send_command("right_click", {"x": x, "y": y}) + + async def double_click(self, x: Optional[int] = None, y: Optional[int] = None) -> None: + await self._send_command("double_click", {"x": x, "y": y}) + + async def move_cursor(self, x: int, y: int) -> None: + await self._send_command("move_cursor", {"x": x, "y": y}) + + async def drag_to(self, x: int, y: int, button: "MouseButton" = "left", duration: float = 0.5) -> None: + await self._send_command( + "drag_to", {"x": x, "y": y, "button": button, "duration": duration} + ) + + async def drag(self, path: List[Tuple[int, int]], button: "MouseButton" = "left", duration: float = 0.5) -> None: + await self._send_command( + "drag", {"path": path, "button": button, "duration": duration} + ) + + # Keyboard Actions + async def key_down(self, key: "KeyType") -> None: + await self._send_command("key_down", {"key": key}) + + async def key_up(self, key: "KeyType") -> None: + await self._send_command("key_up", {"key": key}) + + async def type_text(self, text: str) -> None: + # Temporary fix for https://github.com/trycua/cua/issues/165 + # Check if text contains Unicode characters + if any(ord(char) > 127 for char in text): + # For Unicode text, use clipboard and paste + await self.set_clipboard(text) + await self.hotkey(Key.COMMAND, 'v') + else: + # For ASCII text, use the regular typing method + await self._send_command("type_text", {"text": text}) + + async def press(self, key: "KeyType") -> None: + """Press a single key. + + Args: + key: The key to press. Can be any of: + - A Key enum value (recommended), e.g. Key.PAGE_DOWN + - A direct key value string, e.g. 'pagedown' + - A single character string, e.g. 'a' + + Examples: + ```python + # Using enum (recommended) + await interface.press(Key.PAGE_DOWN) + await interface.press(Key.ENTER) + + # Using direct values + await interface.press('pagedown') + await interface.press('enter') + + # Using single characters + await interface.press('a') + ``` + + Raises: + ValueError: If the key type is invalid or the key is not recognized + """ + if isinstance(key, Key): + actual_key = key.value + elif isinstance(key, str): + # Try to convert to enum if it matches a known key + key_or_enum = Key.from_string(key) + actual_key = key_or_enum.value if isinstance(key_or_enum, Key) else key_or_enum + else: + raise ValueError(f"Invalid key type: {type(key)}. Must be Key enum or string.") + + await self._send_command("press_key", {"key": actual_key}) + + async def press_key(self, key: "KeyType") -> None: + """DEPRECATED: Use press() instead. + + This method is kept for backward compatibility but will be removed in a future version. + Please use the press() method instead. + """ + await self.press(key) + + async def hotkey(self, *keys: "KeyType") -> None: + """Press multiple keys simultaneously. + + Args: + *keys: Multiple keys to press simultaneously. Each key can be any of: + - A Key enum value (recommended), e.g. Key.COMMAND + - A direct key value string, e.g. 'command' + - A single character string, e.g. 'a' + + Examples: + ```python + # Using enums (recommended) + await interface.hotkey(Key.COMMAND, Key.C) # Copy + await interface.hotkey(Key.COMMAND, Key.V) # Paste + + # Using mixed formats + await interface.hotkey(Key.COMMAND, 'a') # Select all + ``` + + Raises: + ValueError: If any key type is invalid or not recognized + """ + actual_keys = [] + for key in keys: + if isinstance(key, Key): + actual_keys.append(key.value) + elif isinstance(key, str): + # Try to convert to enum if it matches a known key + key_or_enum = Key.from_string(key) + actual_keys.append(key_or_enum.value if isinstance(key_or_enum, Key) else key_or_enum) + else: + raise ValueError(f"Invalid key type: {type(key)}. Must be Key enum or string.") + + await self._send_command("hotkey", {"keys": actual_keys}) + + # Scrolling Actions + async def scroll(self, x: int, y: int) -> None: + await self._send_command("scroll", {"x": x, "y": y}) + + async def scroll_down(self, clicks: int = 1) -> None: + await self._send_command("scroll_down", {"clicks": clicks}) + + async def scroll_up(self, clicks: int = 1) -> None: + await self._send_command("scroll_up", {"clicks": clicks}) + + # Screen actions + async def screenshot( + self, + boxes: Optional[List[Tuple[int, int, int, int]]] = None, + box_color: str = "#FF0000", + box_thickness: int = 2, + scale_factor: float = 1.0, + ) -> bytes: + """Take a screenshot with optional box drawing and scaling. + + Args: + boxes: Optional list of (x, y, width, height) tuples defining boxes to draw in screen coordinates + box_color: Color of the boxes in hex format (default: "#FF0000" red) + box_thickness: Thickness of the box borders in pixels (default: 2) + scale_factor: Factor to scale the final image by (default: 1.0) + Use > 1.0 to enlarge, < 1.0 to shrink (e.g., 0.5 for half size, 2.0 for double) + + Returns: + bytes: The screenshot image data, optionally with boxes drawn on it and scaled + """ + result = await self._send_command("screenshot") + if not result.get("image_data"): + raise RuntimeError("Failed to take screenshot") + + screenshot = decode_base64_image(result["image_data"]) + + if boxes: + # Get the natural scaling between screen and screenshot + screen_size = await self.get_screen_size() + screenshot_width, screenshot_height = bytes_to_image(screenshot).size + width_scale = screenshot_width / screen_size["width"] + height_scale = screenshot_height / screen_size["height"] + + # Scale box coordinates from screen space to screenshot space + for box in boxes: + scaled_box = ( + int(box[0] * width_scale), # x + int(box[1] * height_scale), # y + int(box[2] * width_scale), # width + int(box[3] * height_scale), # height + ) + screenshot = draw_box( + screenshot, + x=scaled_box[0], + y=scaled_box[1], + width=scaled_box[2], + height=scaled_box[3], + color=box_color, + thickness=box_thickness, + ) + + if scale_factor != 1.0: + screenshot = resize_image(screenshot, scale_factor) + + return screenshot + + async def get_screen_size(self) -> Dict[str, int]: + result = await self._send_command("get_screen_size") + if result["success"] and result["size"]: + return result["size"] + raise RuntimeError("Failed to get screen size") + + async def get_cursor_position(self) -> Dict[str, int]: + result = await self._send_command("get_cursor_position") + if result["success"] and result["position"]: + return result["position"] + raise RuntimeError("Failed to get cursor position") + + # Clipboard Actions + async def copy_to_clipboard(self) -> str: + result = await self._send_command("copy_to_clipboard") + if result["success"] and result["content"]: + return result["content"] + raise RuntimeError("Failed to get clipboard content") + + async def set_clipboard(self, text: str) -> None: + await self._send_command("set_clipboard", {"text": text}) + + # File Operations + async def _write_bytes_chunked(self, path: str, content: bytes, append: bool = False, chunk_size: int = 1024 * 1024) -> None: + """Write large files in chunks to avoid memory issues.""" + total_size = len(content) + current_offset = 0 + + while current_offset < total_size: + chunk_end = min(current_offset + chunk_size, total_size) + chunk_data = content[current_offset:chunk_end] + + # First chunk uses the original append flag, subsequent chunks always append + chunk_append = append if current_offset == 0 else True + + result = await self._send_command("write_bytes", { + "path": path, + "content_b64": encode_base64_image(chunk_data), + "append": chunk_append + }) + + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to write file chunk")) + + current_offset = chunk_end + + async def write_bytes(self, path: str, content: bytes, append: bool = False) -> None: + # For large files, use chunked writing + if len(content) > 5 * 1024 * 1024: # 5MB threshold + await self._write_bytes_chunked(path, content, append) + return + + result = await self._send_command("write_bytes", {"path": path, "content_b64": encode_base64_image(content), "append": append}) + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to write file")) + + async def _read_bytes_chunked(self, path: str, offset: int, total_length: int, chunk_size: int = 1024 * 1024) -> bytes: + """Read large files in chunks to avoid memory issues.""" + chunks = [] + current_offset = offset + remaining = total_length + + while remaining > 0: + read_size = min(chunk_size, remaining) + result = await self._send_command("read_bytes", { + "path": path, + "offset": current_offset, + "length": read_size + }) + + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to read file chunk")) + + content_b64 = result.get("content_b64", "") + chunk_data = decode_base64_image(content_b64) + chunks.append(chunk_data) + + current_offset += read_size + remaining -= read_size + + return b''.join(chunks) + + async def read_bytes(self, path: str, offset: int = 0, length: Optional[int] = None) -> bytes: + # For large files, use chunked reading + if length is None: + # Get file size first to determine if we need chunking + file_size = await self.get_file_size(path) + # If file is larger than 5MB, read in chunks + if file_size > 5 * 1024 * 1024: # 5MB threshold + return await self._read_bytes_chunked(path, offset, file_size - offset if offset > 0 else file_size) + + result = await self._send_command("read_bytes", { + "path": path, + "offset": offset, + "length": length + }) + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to read file")) + content_b64 = result.get("content_b64", "") + return decode_base64_image(content_b64) + + async def read_text(self, path: str) -> str: + result = await self._send_command("read_text", {"path": path}) + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to read file")) + return result.get("content", "") + + async def write_text(self, path: str, content: str) -> None: + result = await self._send_command("write_text", {"path": path, "content": content}) + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to write file")) + + async def get_file_size(self, path: str) -> int: + result = await self._send_command("get_file_size", {"path": path}) + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to get file size")) + return result.get("size", 0) + + async def file_exists(self, path: str) -> bool: + result = await self._send_command("file_exists", {"path": path}) + return result.get("exists", False) + + async def directory_exists(self, path: str) -> bool: + result = await self._send_command("directory_exists", {"path": path}) + return result.get("exists", False) + + async def create_dir(self, path: str) -> None: + result = await self._send_command("create_dir", {"path": path}) + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to create directory")) + + async def delete_file(self, path: str) -> None: + result = await self._send_command("delete_file", {"path": path}) + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to delete file")) + + async def delete_dir(self, path: str) -> None: + result = await self._send_command("delete_dir", {"path": path}) + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to delete directory")) + + async def list_dir(self, path: str) -> list[str]: + result = await self._send_command("list_dir", {"path": path}) + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to list directory")) + return result.get("files", []) + + # Command execution + async def run_command(self, command: str) -> CommandResult: + result = await self._send_command("run_command", {"command": command}) + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to run command")) + return CommandResult( + stdout=result.get("stdout", ""), + stderr=result.get("stderr", ""), + returncode=result.get("return_code", 0) + ) + + # Accessibility Actions + async def get_accessibility_tree(self) -> Dict[str, Any]: + """Get the accessibility tree of the current screen.""" + result = await self._send_command("get_accessibility_tree") + if not result.get("success", False): + raise RuntimeError(result.get("error", "Failed to get accessibility tree")) + return result + + async def get_active_window_bounds(self) -> Dict[str, int]: + """Get the bounds of the currently active window.""" + result = await self._send_command("get_active_window_bounds") + if result["success"] and result["bounds"]: + return result["bounds"] + raise RuntimeError("Failed to get active window bounds") + + async def to_screen_coordinates(self, x: float, y: float) -> tuple[float, float]: + """Convert screenshot coordinates to screen coordinates. + + Args: + x: X coordinate in screenshot space + y: Y coordinate in screenshot space + + Returns: + tuple[float, float]: (x, y) coordinates in screen space + """ + screen_size = await self.get_screen_size() + screenshot = await self.screenshot() + screenshot_img = bytes_to_image(screenshot) + screenshot_width, screenshot_height = screenshot_img.size + + # Calculate scaling factors + width_scale = screen_size["width"] / screenshot_width + height_scale = screen_size["height"] / screenshot_height + + # Convert coordinates + screen_x = x * width_scale + screen_y = y * height_scale + + return screen_x, screen_y + + async def to_screenshot_coordinates(self, x: float, y: float) -> tuple[float, float]: + """Convert screen coordinates to screenshot coordinates. + + Args: + x: X coordinate in screen space + y: Y coordinate in screen space + + Returns: + tuple[float, float]: (x, y) coordinates in screenshot space + """ + screen_size = await self.get_screen_size() + screenshot = await self.screenshot() + screenshot_img = bytes_to_image(screenshot) + screenshot_width, screenshot_height = screenshot_img.size + + # Calculate scaling factors + width_scale = screenshot_width / screen_size["width"] + height_scale = screenshot_height / screen_size["height"] + + # Convert coordinates + screenshot_x = x * width_scale + screenshot_y = y * height_scale + + return screenshot_x, screenshot_y + + # Websocket Methods + async def _keep_alive(self): + """Keep the WebSocket connection alive with automatic reconnection.""" + retry_count = 0 + max_log_attempts = 1 # Only log the first attempt at INFO level + log_interval = 500 # Then log every 500th attempt (significantly increased from 30) + last_warning_time = 0 + min_warning_interval = 30 # Minimum seconds between connection lost warnings + min_retry_delay = 0.5 # Minimum delay between connection attempts (500ms) + + while not self._closed: + try: + if self._ws is None or ( + self._ws and self._ws.state == websockets.protocol.State.CLOSED + ): + try: + retry_count += 1 + + # Add a minimum delay between connection attempts to avoid flooding + if retry_count > 1: + await asyncio.sleep(min_retry_delay) + + # Only log the first attempt at INFO level, then every Nth attempt + if retry_count == 1: + self.logger.info(f"Attempting WebSocket connection to {self.ws_uri}") + elif retry_count % log_interval == 0: + self.logger.info( + f"Still attempting WebSocket connection (attempt {retry_count})..." + ) + else: + # All other attempts are logged at DEBUG level + self.logger.debug( + f"Attempting WebSocket connection to {self.ws_uri} (attempt {retry_count})" + ) + + self._ws = await asyncio.wait_for( + websockets.connect( + self.ws_uri, + max_size=1024 * 1024 * 10, # 10MB limit + max_queue=32, + ping_interval=self._ping_interval, + ping_timeout=self._ping_timeout, + close_timeout=5, + compression=None, # Disable compression to reduce overhead + ), + timeout=120, + ) + self.logger.info("WebSocket connection established") + + # If api_key and vm_name are provided, perform authentication handshake + if self.api_key and self.vm_name: + self.logger.info("Performing authentication handshake...") + auth_message = { + "command": "authenticate", + "params": { + "api_key": self.api_key, + "container_name": self.vm_name + } + } + await self._ws.send(json.dumps(auth_message)) + + # Wait for authentication response + auth_response = await asyncio.wait_for(self._ws.recv(), timeout=10) + auth_result = json.loads(auth_response) + + if not auth_result.get("success"): + error_msg = auth_result.get("error", "Authentication failed") + self.logger.error(f"Authentication failed: {error_msg}") + await self._ws.close() + self._ws = None + raise ConnectionError(f"Authentication failed: {error_msg}") + + self.logger.info("Authentication successful") + + self._reconnect_delay = 1 # Reset reconnect delay on successful connection + self._last_ping = time.time() + retry_count = 0 # Reset retry count on successful connection + except (asyncio.TimeoutError, websockets.exceptions.WebSocketException) as e: + next_retry = self._reconnect_delay + + # Only log the first error at WARNING level, then every Nth attempt + if retry_count == 1: + self.logger.warning( + f"Computer API Server not ready yet. Will retry automatically." + ) + elif retry_count % log_interval == 0: + self.logger.warning( + f"Still waiting for Computer API Server (attempt {retry_count})..." + ) + else: + # All other errors are logged at DEBUG level + self.logger.debug(f"Connection attempt {retry_count} failed: {e}") + + if self._ws: + try: + await self._ws.close() + except: + pass + self._ws = None + + # Use exponential backoff for connection retries + await asyncio.sleep(self._reconnect_delay) + self._reconnect_delay = min( + self._reconnect_delay * 2, self._max_reconnect_delay + ) + continue + + # Regular ping to check connection + if self._ws and self._ws.state == websockets.protocol.State.OPEN: + try: + if time.time() - self._last_ping >= self._ping_interval: + pong_waiter = await self._ws.ping() + await asyncio.wait_for(pong_waiter, timeout=self._ping_timeout) + self._last_ping = time.time() + except Exception as e: + self.logger.debug(f"Ping failed: {e}") + if self._ws: + try: + await self._ws.close() + except: + pass + self._ws = None + continue + + await asyncio.sleep(1) + + except Exception as e: + current_time = time.time() + # Only log connection lost warnings at most once every min_warning_interval seconds + if current_time - last_warning_time >= min_warning_interval: + self.logger.warning( + f"Computer API Server connection lost. Will retry automatically." + ) + last_warning_time = current_time + else: + # Log at debug level instead + self.logger.debug(f"Connection lost: {e}") + + if self._ws: + try: + await self._ws.close() + except: + pass + self._ws = None + + async def _ensure_connection(self): + """Ensure WebSocket connection is established.""" + if self._reconnect_task is None or self._reconnect_task.done(): + self._reconnect_task = asyncio.create_task(self._keep_alive()) + + retry_count = 0 + max_retries = 5 + + while retry_count < max_retries: + try: + if self._ws and self._ws.state == websockets.protocol.State.OPEN: + return + retry_count += 1 + await asyncio.sleep(1) + except Exception as e: + # Only log at ERROR level for the last retry attempt + if retry_count == max_retries - 1: + self.logger.error( + f"Persistent connection check error after {retry_count} attempts: {e}" + ) + else: + self.logger.debug(f"Connection check error (attempt {retry_count}): {e}") + retry_count += 1 + await asyncio.sleep(1) + continue + + raise ConnectionError("Failed to establish WebSocket connection after multiple retries") + + async def _send_command(self, command: str, params: Optional[Dict] = None) -> Dict[str, Any]: + """Send command through WebSocket.""" + max_retries = 3 + retry_count = 0 + last_error = None + + # Acquire lock to ensure only one command is processed at a time + async with self._command_lock: + self.logger.debug(f"Acquired lock for command: {command}") + while retry_count < max_retries: + try: + await self._ensure_connection() + if not self._ws: + raise ConnectionError("WebSocket connection is not established") + + message = {"command": command, "params": params or {}} + await self._ws.send(json.dumps(message)) + response = await asyncio.wait_for(self._ws.recv(), timeout=120) + self.logger.debug(f"Completed command: {command}") + return json.loads(response) + except Exception as e: + last_error = e + retry_count += 1 + if retry_count < max_retries: + # Only log at debug level for intermediate retries + self.logger.debug( + f"Command '{command}' failed (attempt {retry_count}/{max_retries}): {e}" + ) + await asyncio.sleep(1) + continue + else: + # Only log at error level for the final failure + self.logger.error( + f"Failed to send command '{command}' after {max_retries} retries" + ) + self.logger.debug(f"Command failure details: {e}") + raise + + raise last_error if last_error else RuntimeError("Failed to send command") + + async def wait_for_ready(self, timeout: int = 60, interval: float = 1.0): + """Wait for WebSocket connection to become available.""" + start_time = time.time() + last_error = None + attempt_count = 0 + progress_interval = 10 # Log progress every 10 seconds + last_progress_time = start_time + + # Disable detailed logging for connection attempts + self._log_connection_attempts = False + + try: + self.logger.info( + f"Waiting for Computer API Server to be ready (timeout: {timeout}s)..." + ) + + # Start the keep-alive task if it's not already running + if self._reconnect_task is None or self._reconnect_task.done(): + self._reconnect_task = asyncio.create_task(self._keep_alive()) + + # Wait for the connection to be established + while time.time() - start_time < timeout: + try: + attempt_count += 1 + current_time = time.time() + + # Log progress periodically without flooding logs + if current_time - last_progress_time >= progress_interval: + elapsed = current_time - start_time + self.logger.info( + f"Still waiting for Computer API Server... (elapsed: {elapsed:.1f}s, attempts: {attempt_count})" + ) + last_progress_time = current_time + + # Check if we have a connection + if self._ws and self._ws.state == websockets.protocol.State.OPEN: + # Test the connection with a simple command + try: + await self._send_command("get_screen_size") + elapsed = time.time() - start_time + self.logger.info( + f"Computer API Server is ready (after {elapsed:.1f}s, {attempt_count} attempts)" + ) + return # Connection is fully working + except Exception as e: + last_error = e + self.logger.debug(f"Connection test failed: {e}") + + # Wait before trying again + await asyncio.sleep(interval) + + except Exception as e: + last_error = e + self.logger.debug(f"Connection attempt {attempt_count} failed: {e}") + await asyncio.sleep(interval) + + # If we get here, we've timed out + error_msg = f"Could not connect to {self.ip_address} after {timeout} seconds" + if last_error: + error_msg += f": {str(last_error)}" + self.logger.error(error_msg) + raise TimeoutError(error_msg) + finally: + # Reset to default logging behavior + self._log_connection_attempts = False + + def close(self): + """Close WebSocket connection. + + Note: In host computer server mode, we leave the connection open + to allow other clients to connect to the same server. The server + will handle cleaning up idle connections. + """ + # Only cancel the reconnect task + if self._reconnect_task: + self._reconnect_task.cancel() + + # Don't set closed flag or close websocket by default + # This allows the server to stay connected for other clients + # self._closed = True + # if self._ws: + # asyncio.create_task(self._ws.close()) + # self._ws = None + + def force_close(self): + """Force close the WebSocket connection. + + This method should be called when you want to completely + shut down the connection, not just for regular cleanup. + """ + self._closed = True + if self._reconnect_task: + self._reconnect_task.cancel() + if self._ws: + asyncio.create_task(self._ws.close()) + self._ws = None + diff --git a/libs/python/computer/computer/interface/linux.py b/libs/python/computer/computer/interface/linux.py index 11840765..174fe07a 100644 --- a/libs/python/computer/computer/interface/linux.py +++ b/libs/python/computer/computer/interface/linux.py @@ -1,735 +1,8 @@ -import asyncio -import json -import time -from typing import Any, Dict, List, Optional, Tuple -from PIL import Image +from typing import Optional +from .generic import GenericComputerInterface -import websockets - -from ..logger import Logger, LogLevel -from .base import BaseComputerInterface -from ..utils import decode_base64_image, encode_base64_image, bytes_to_image, draw_box, resize_image -from .models import Key, KeyType, MouseButton, CommandResult - -class LinuxComputerInterface(BaseComputerInterface): +class LinuxComputerInterface(GenericComputerInterface): """Interface for Linux.""" def __init__(self, ip_address: str, username: str = "lume", password: str = "lume", api_key: Optional[str] = None, vm_name: Optional[str] = None): - super().__init__(ip_address, username, password, api_key, vm_name) - self._ws = None - self._reconnect_task = None - self._closed = False - self._last_ping = 0 - self._ping_interval = 5 # Send ping every 5 seconds - self._ping_timeout = 120 # Wait 120 seconds for pong response - self._reconnect_delay = 1 # Start with 1 second delay - self._max_reconnect_delay = 30 # Maximum delay between reconnection attempts - self._log_connection_attempts = True # Flag to control connection attempt logging - self._authenticated = False # Track authentication status - self._command_lock = asyncio.Lock() # Lock to ensure only one command at a time - - # Set logger name for Linux interface - self.logger = Logger("computer.interface.linux", LogLevel.NORMAL) - - @property - def ws_uri(self) -> str: - """Get the WebSocket URI using the current IP address. - - Returns: - WebSocket URI for the Computer API Server - """ - protocol = "wss" if self.api_key else "ws" - port = "8443" if self.api_key else "8000" - return f"{protocol}://{self.ip_address}:{port}/ws" - - async def _keep_alive(self): - """Keep the WebSocket connection alive with automatic reconnection.""" - retry_count = 0 - max_log_attempts = 1 # Only log the first attempt at INFO level - log_interval = 500 # Then log every 500th attempt (significantly increased from 30) - last_warning_time = 0 - min_warning_interval = 30 # Minimum seconds between connection lost warnings - min_retry_delay = 0.5 # Minimum delay between connection attempts (500ms) - - while not self._closed: - try: - if self._ws is None or ( - self._ws and self._ws.state == websockets.protocol.State.CLOSED - ): - try: - retry_count += 1 - - # Add a minimum delay between connection attempts to avoid flooding - if retry_count > 1: - await asyncio.sleep(min_retry_delay) - - # Only log the first attempt at INFO level, then every Nth attempt - if retry_count == 1: - self.logger.info(f"Attempting WebSocket connection to {self.ws_uri}") - elif retry_count % log_interval == 0: - self.logger.info( - f"Still attempting WebSocket connection (attempt {retry_count})..." - ) - else: - # All other attempts are logged at DEBUG level - self.logger.debug( - f"Attempting WebSocket connection to {self.ws_uri} (attempt {retry_count})" - ) - - self._ws = await asyncio.wait_for( - websockets.connect( - self.ws_uri, - max_size=1024 * 1024 * 10, # 10MB limit - max_queue=32, - ping_interval=self._ping_interval, - ping_timeout=self._ping_timeout, - close_timeout=5, - compression=None, # Disable compression to reduce overhead - ), - timeout=120, - ) - self.logger.info("WebSocket connection established") - - # Authentication will be handled by the first command that needs it - # Don't do authentication here to avoid recv conflicts - - self._reconnect_delay = 1 # Reset reconnect delay on successful connection - self._last_ping = time.time() - retry_count = 0 # Reset retry count on successful connection - self._authenticated = False # Reset auth status on new connection - - except (asyncio.TimeoutError, websockets.exceptions.WebSocketException) as e: - next_retry = self._reconnect_delay - - # Only log the first error at WARNING level, then every Nth attempt - if retry_count == 1: - self.logger.warning( - f"Computer API Server not ready yet. Will retry automatically." - ) - elif retry_count % log_interval == 0: - self.logger.warning( - f"Still waiting for Computer API Server (attempt {retry_count})..." - ) - else: - # All other errors are logged at DEBUG level - self.logger.debug(f"Connection attempt {retry_count} failed: {e}") - - if self._ws: - try: - await self._ws.close() - except: - pass - self._ws = None - - # Regular ping to check connection - if self._ws and self._ws.state == websockets.protocol.State.OPEN: - try: - if time.time() - self._last_ping >= self._ping_interval: - pong_waiter = await self._ws.ping() - await asyncio.wait_for(pong_waiter, timeout=self._ping_timeout) - self._last_ping = time.time() - except Exception as e: - self.logger.debug(f"Ping failed: {e}") - if self._ws: - try: - await self._ws.close() - except: - pass - self._ws = None - continue - - await asyncio.sleep(1) - - except Exception as e: - current_time = time.time() - # Only log connection lost warnings at most once every min_warning_interval seconds - if current_time - last_warning_time >= min_warning_interval: - self.logger.warning( - f"Computer API Server connection lost. Will retry automatically." - ) - last_warning_time = current_time - else: - # Log at debug level instead - self.logger.debug(f"Connection lost: {e}") - - if self._ws: - try: - await self._ws.close() - except: - pass - self._ws = None - - async def _ensure_connection(self): - """Ensure WebSocket connection is established.""" - if self._reconnect_task is None or self._reconnect_task.done(): - self._reconnect_task = asyncio.create_task(self._keep_alive()) - - retry_count = 0 - max_retries = 5 - - while retry_count < max_retries: - try: - if self._ws and self._ws.state == websockets.protocol.State.OPEN: - return - retry_count += 1 - await asyncio.sleep(1) - except Exception as e: - # Only log at ERROR level for the last retry attempt - if retry_count == max_retries - 1: - self.logger.error( - f"Persistent connection check error after {retry_count} attempts: {e}" - ) - else: - self.logger.debug(f"Connection check error (attempt {retry_count}): {e}") - retry_count += 1 - await asyncio.sleep(1) - continue - - raise ConnectionError("Failed to establish WebSocket connection after multiple retries") - - async def _send_command(self, command: str, params: Optional[Dict] = None) -> Dict[str, Any]: - """Send command through WebSocket.""" - max_retries = 3 - retry_count = 0 - last_error = None - - # Acquire lock to ensure only one command is processed at a time - async with self._command_lock: - self.logger.debug(f"Acquired lock for command: {command}") - while retry_count < max_retries: - try: - await self._ensure_connection() - if not self._ws: - raise ConnectionError("WebSocket connection is not established") - - # Handle authentication if needed - if self.api_key and self.vm_name and not self._authenticated: - self.logger.info("Performing authentication handshake...") - auth_message = { - "command": "authenticate", - "params": { - "api_key": self.api_key, - "container_name": self.vm_name - } - } - await self._ws.send(json.dumps(auth_message)) - - # Wait for authentication response - auth_response = await asyncio.wait_for(self._ws.recv(), timeout=10) - auth_result = json.loads(auth_response) - - if not auth_result.get("success"): - error_msg = auth_result.get("error", "Authentication failed") - self.logger.error(f"Authentication failed: {error_msg}") - self._authenticated = False - raise ConnectionError(f"Authentication failed: {error_msg}") - - self.logger.info("Authentication successful") - self._authenticated = True - - message = {"command": command, "params": params or {}} - await self._ws.send(json.dumps(message)) - response = await asyncio.wait_for(self._ws.recv(), timeout=30) - self.logger.debug(f"Completed command: {command}") - return json.loads(response) - except Exception as e: - last_error = e - retry_count += 1 - if retry_count < max_retries: - # Only log at debug level for intermediate retries - self.logger.debug( - f"Command '{command}' failed (attempt {retry_count}/{max_retries}): {e}" - ) - await asyncio.sleep(1) - continue - else: - # Only log at error level for the final failure - self.logger.error( - f"Failed to send command '{command}' after {max_retries} retries" - ) - self.logger.debug(f"Command failure details: {e}") - raise last_error if last_error else RuntimeError("Failed to send command") - - async def wait_for_ready(self, timeout: int = 60, interval: float = 1.0): - """Wait for WebSocket connection to become available.""" - start_time = time.time() - last_error = None - attempt_count = 0 - progress_interval = 10 # Log progress every 10 seconds - last_progress_time = start_time - - # Disable detailed logging for connection attempts - self._log_connection_attempts = False - - try: - self.logger.info( - f"Waiting for Computer API Server to be ready (timeout: {timeout}s)..." - ) - - # Start the keep-alive task if it's not already running - if self._reconnect_task is None or self._reconnect_task.done(): - self._reconnect_task = asyncio.create_task(self._keep_alive()) - - # Wait for the connection to be established - while time.time() - start_time < timeout: - try: - attempt_count += 1 - current_time = time.time() - - # Log progress periodically without flooding logs - if current_time - last_progress_time >= progress_interval: - elapsed = current_time - start_time - self.logger.info( - f"Still waiting for Computer API Server... (elapsed: {elapsed:.1f}s, attempts: {attempt_count})" - ) - last_progress_time = current_time - - # Check if we have a connection - if self._ws and self._ws.state == websockets.protocol.State.OPEN: - # Test the connection with a simple command - try: - await self._send_command("get_screen_size") - elapsed = time.time() - start_time - self.logger.info( - f"Computer API Server is ready (after {elapsed:.1f}s, {attempt_count} attempts)" - ) - return # Connection is fully working - except Exception as e: - last_error = e - self.logger.debug(f"Connection test failed: {e}") - - # Wait before trying again - await asyncio.sleep(interval) - - except Exception as e: - last_error = e - self.logger.debug(f"Connection attempt {attempt_count} failed: {e}") - await asyncio.sleep(interval) - - # If we get here, we've timed out - error_msg = f"Could not connect to {self.ip_address} after {timeout} seconds" - if last_error: - error_msg += f": {str(last_error)}" - self.logger.error(error_msg) - raise TimeoutError(error_msg) - finally: - # Reset to default logging behavior - self._log_connection_attempts = False - - def close(self): - """Close WebSocket connection. - - Note: In host computer server mode, we leave the connection open - to allow other clients to connect to the same server. The server - will handle cleaning up idle connections. - """ - # Only cancel the reconnect task - if self._reconnect_task: - self._reconnect_task.cancel() - - # Don't set closed flag or close websocket by default - # This allows the server to stay connected for other clients - # self._closed = True - # if self._ws: - # asyncio.create_task(self._ws.close()) - # self._ws = None - - def force_close(self): - """Force close the WebSocket connection. - - This method should be called when you want to completely - shut down the connection, not just for regular cleanup. - """ - self._closed = True - if self._reconnect_task: - self._reconnect_task.cancel() - if self._ws: - asyncio.create_task(self._ws.close()) - self._ws = None - - # Mouse Actions - async def mouse_down(self, x: Optional[int] = None, y: Optional[int] = None, button: str = "left") -> None: - await self._send_command("mouse_down", {"x": x, "y": y, "button": button}) - - async def mouse_up(self, x: Optional[int] = None, y: Optional[int] = None, button: str = "left") -> None: - await self._send_command("mouse_up", {"x": x, "y": y, "button": button}) - - async def left_click(self, x: Optional[int] = None, y: Optional[int] = None) -> None: - await self._send_command("left_click", {"x": x, "y": y}) - - async def right_click(self, x: Optional[int] = None, y: Optional[int] = None) -> None: - await self._send_command("right_click", {"x": x, "y": y}) - - async def double_click(self, x: Optional[int] = None, y: Optional[int] = None) -> None: - await self._send_command("double_click", {"x": x, "y": y}) - - async def move_cursor(self, x: int, y: int) -> None: - await self._send_command("move_cursor", {"x": x, "y": y}) - - async def drag_to(self, x: int, y: int, button: "MouseButton" = "left", duration: float = 0.5) -> None: - await self._send_command( - "drag_to", {"x": x, "y": y, "button": button, "duration": duration} - ) - - async def drag(self, path: List[Tuple[int, int]], button: "MouseButton" = "left", duration: float = 0.5) -> None: - await self._send_command( - "drag", {"path": path, "button": button, "duration": duration} - ) - - # Keyboard Actions - async def key_down(self, key: "KeyType") -> None: - await self._send_command("key_down", {"key": key}) - - async def key_up(self, key: "KeyType") -> None: - await self._send_command("key_up", {"key": key}) - - async def type_text(self, text: str) -> None: - # Temporary fix for https://github.com/trycua/cua/issues/165 - # Check if text contains Unicode characters - if any(ord(char) > 127 for char in text): - # For Unicode text, use clipboard and paste - await self.set_clipboard(text) - await self.hotkey(Key.COMMAND, 'v') - else: - # For ASCII text, use the regular typing method - await self._send_command("type_text", {"text": text}) - - async def press(self, key: "KeyType") -> None: - """Press a single key. - - Args: - key: The key to press. Can be any of: - - A Key enum value (recommended), e.g. Key.PAGE_DOWN - - A direct key value string, e.g. 'pagedown' - - A single character string, e.g. 'a' - - Examples: - ```python - # Using enum (recommended) - await interface.press(Key.PAGE_DOWN) - await interface.press(Key.ENTER) - - # Using direct values - await interface.press('pagedown') - await interface.press('enter') - - # Using single characters - await interface.press('a') - ``` - - Raises: - ValueError: If the key type is invalid or the key is not recognized - """ - if isinstance(key, Key): - actual_key = key.value - elif isinstance(key, str): - # Try to convert to enum if it matches a known key - key_or_enum = Key.from_string(key) - actual_key = key_or_enum.value if isinstance(key_or_enum, Key) else key_or_enum - else: - raise ValueError(f"Invalid key type: {type(key)}. Must be Key enum or string.") - - await self._send_command("press_key", {"key": actual_key}) - - async def press_key(self, key: "KeyType") -> None: - """DEPRECATED: Use press() instead. - - This method is kept for backward compatibility but will be removed in a future version. - Please use the press() method instead. - """ - await self.press(key) - - async def hotkey(self, *keys: "KeyType") -> None: - """Press multiple keys simultaneously. - - Args: - *keys: Multiple keys to press simultaneously. Each key can be any of: - - A Key enum value (recommended), e.g. Key.COMMAND - - A direct key value string, e.g. 'command' - - A single character string, e.g. 'a' - - Examples: - ```python - # Using enums (recommended) - await interface.hotkey(Key.COMMAND, Key.C) # Copy - await interface.hotkey(Key.COMMAND, Key.V) # Paste - - # Using mixed formats - await interface.hotkey(Key.COMMAND, 'a') # Select all - ``` - - Raises: - ValueError: If any key type is invalid or not recognized - """ - actual_keys = [] - for key in keys: - if isinstance(key, Key): - actual_keys.append(key.value) - elif isinstance(key, str): - # Try to convert to enum if it matches a known key - key_or_enum = Key.from_string(key) - actual_keys.append(key_or_enum.value if isinstance(key_or_enum, Key) else key_or_enum) - else: - raise ValueError(f"Invalid key type: {type(key)}. Must be Key enum or string.") - - await self._send_command("hotkey", {"keys": actual_keys}) - - # Scrolling Actions - async def scroll(self, x: int, y: int) -> None: - await self._send_command("scroll", {"x": x, "y": y}) - - async def scroll_down(self, clicks: int = 1) -> None: - await self._send_command("scroll_down", {"clicks": clicks}) - - async def scroll_up(self, clicks: int = 1) -> None: - await self._send_command("scroll_up", {"clicks": clicks}) - - # Screen Actions - async def screenshot( - self, - boxes: Optional[List[Tuple[int, int, int, int]]] = None, - box_color: str = "#FF0000", - box_thickness: int = 2, - scale_factor: float = 1.0, - ) -> bytes: - """Take a screenshot with optional box drawing and scaling. - - Args: - boxes: Optional list of (x, y, width, height) tuples defining boxes to draw in screen coordinates - box_color: Color of the boxes in hex format (default: "#FF0000" red) - box_thickness: Thickness of the box borders in pixels (default: 2) - scale_factor: Factor to scale the final image by (default: 1.0) - Use > 1.0 to enlarge, < 1.0 to shrink (e.g., 0.5 for half size, 2.0 for double) - - Returns: - bytes: The screenshot image data, optionally with boxes drawn on it and scaled - """ - result = await self._send_command("screenshot") - if not result.get("image_data"): - raise RuntimeError("Failed to take screenshot") - - screenshot = decode_base64_image(result["image_data"]) - - if boxes: - # Get the natural scaling between screen and screenshot - screen_size = await self.get_screen_size() - screenshot_width, screenshot_height = bytes_to_image(screenshot).size - width_scale = screenshot_width / screen_size["width"] - height_scale = screenshot_height / screen_size["height"] - - # Scale box coordinates from screen space to screenshot space - for box in boxes: - scaled_box = ( - int(box[0] * width_scale), # x - int(box[1] * height_scale), # y - int(box[2] * width_scale), # width - int(box[3] * height_scale), # height - ) - screenshot = draw_box( - screenshot, - x=scaled_box[0], - y=scaled_box[1], - width=scaled_box[2], - height=scaled_box[3], - color=box_color, - thickness=box_thickness, - ) - - if scale_factor != 1.0: - screenshot = resize_image(screenshot, scale_factor) - - return screenshot - - async def get_screen_size(self) -> Dict[str, int]: - result = await self._send_command("get_screen_size") - if result["success"] and result["size"]: - return result["size"] - raise RuntimeError("Failed to get screen size") - - async def get_cursor_position(self) -> Dict[str, int]: - result = await self._send_command("get_cursor_position") - if result["success"] and result["position"]: - return result["position"] - raise RuntimeError("Failed to get cursor position") - - # Clipboard Actions - async def copy_to_clipboard(self) -> str: - result = await self._send_command("copy_to_clipboard") - if result["success"] and result["content"]: - return result["content"] - raise RuntimeError("Failed to get clipboard content") - - async def set_clipboard(self, text: str) -> None: - await self._send_command("set_clipboard", {"text": text}) - - # File System Actions - async def file_exists(self, path: str) -> bool: - result = await self._send_command("file_exists", {"path": path}) - return result.get("exists", False) - - async def directory_exists(self, path: str) -> bool: - result = await self._send_command("directory_exists", {"path": path}) - return result.get("exists", False) - - async def list_dir(self, path: str) -> list[str]: - result = await self._send_command("list_dir", {"path": path}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to list directory")) - return result.get("files", []) - - async def read_text(self, path: str) -> str: - result = await self._send_command("read_text", {"path": path}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to read file")) - return result.get("content", "") - - async def write_text(self, path: str, content: str) -> None: - result = await self._send_command("write_text", {"path": path, "content": content}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to write file")) - - async def read_bytes(self, path: str, offset: int = 0, length: Optional[int] = None) -> bytes: - # For large files, use chunked reading - if length is None: - # Get file size first to determine if we need chunking - file_size = await self.get_file_size(path) - # If file is larger than 5MB, read in chunks - if file_size > 5 * 1024 * 1024: # 5MB threshold - return await self._read_bytes_chunked(path, offset, file_size - offset if offset > 0 else file_size) - - result = await self._send_command("read_bytes", { - "path": path, - "offset": offset, - "length": length - }) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to read file")) - content_b64 = result.get("content_b64", "") - return decode_base64_image(content_b64) - - async def get_file_size(self, path: str) -> int: - result = await self._send_command("get_file_size", {"path": path}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to get file size")) - return result.get("size", 0) - - async def _read_bytes_chunked(self, path: str, offset: int, total_length: int, chunk_size: int = 1024 * 1024) -> bytes: - """Read large files in chunks to avoid memory issues.""" - chunks = [] - current_offset = offset - remaining = total_length - - while remaining > 0: - read_size = min(chunk_size, remaining) - result = await self._send_command("read_bytes", { - "path": path, - "offset": current_offset, - "length": read_size - }) - - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to read file chunk")) - - content_b64 = result.get("content_b64", "") - chunk_data = decode_base64_image(content_b64) - chunks.append(chunk_data) - - current_offset += read_size - remaining -= read_size - - return b''.join(chunks) - - async def write_bytes(self, path: str, content: bytes) -> None: - result = await self._send_command("write_bytes", {"path": path, "content_b64": encode_base64_image(content)}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to write file")) - - async def delete_file(self, path: str) -> None: - result = await self._send_command("delete_file", {"path": path}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to delete file")) - - async def create_dir(self, path: str) -> None: - result = await self._send_command("create_dir", {"path": path}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to create directory")) - - async def delete_dir(self, path: str) -> None: - result = await self._send_command("delete_dir", {"path": path}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to delete directory")) - - async def run_command(self, command: str) -> CommandResult: - result = await self._send_command("run_command", {"command": command}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to run command")) - return CommandResult( - stdout=result.get("stdout", ""), - stderr=result.get("stderr", ""), - returncode=result.get("return_code", 0) - ) - - # Accessibility Actions - async def get_accessibility_tree(self) -> Dict[str, Any]: - """Get the accessibility tree of the current screen.""" - result = await self._send_command("get_accessibility_tree") - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to get accessibility tree")) - return result - - async def get_active_window_bounds(self) -> Dict[str, int]: - """Get the bounds of the currently active window.""" - result = await self._send_command("get_active_window_bounds") - if result["success"] and result["bounds"]: - return result["bounds"] - raise RuntimeError("Failed to get active window bounds") - - async def to_screen_coordinates(self, x: float, y: float) -> tuple[float, float]: - """Convert screenshot coordinates to screen coordinates. - - Args: - x: X coordinate in screenshot space - y: Y coordinate in screenshot space - - Returns: - tuple[float, float]: (x, y) coordinates in screen space - """ - screen_size = await self.get_screen_size() - screenshot = await self.screenshot() - screenshot_img = bytes_to_image(screenshot) - screenshot_width, screenshot_height = screenshot_img.size - - # Calculate scaling factors - width_scale = screen_size["width"] / screenshot_width - height_scale = screen_size["height"] / screenshot_height - - # Convert coordinates - screen_x = x * width_scale - screen_y = y * height_scale - - return screen_x, screen_y - - async def to_screenshot_coordinates(self, x: float, y: float) -> tuple[float, float]: - """Convert screen coordinates to screenshot coordinates. - - Args: - x: X coordinate in screen space - y: Y coordinate in screen space - - Returns: - tuple[float, float]: (x, y) coordinates in screenshot space - """ - screen_size = await self.get_screen_size() - screenshot = await self.screenshot() - screenshot_img = bytes_to_image(screenshot) - screenshot_width, screenshot_height = screenshot_img.size - - # Calculate scaling factors - width_scale = screenshot_width / screen_size["width"] - height_scale = screenshot_height / screen_size["height"] - - # Convert coordinates - screenshot_x = x * width_scale - screenshot_y = y * height_scale - - return screenshot_x, screenshot_y + super().__init__(ip_address, username, password, api_key, vm_name, "computer.interface.linux") diff --git a/libs/python/computer/computer/interface/macos.py b/libs/python/computer/computer/interface/macos.py index 4419cabf..f4d03a52 100644 --- a/libs/python/computer/computer/interface/macos.py +++ b/libs/python/computer/computer/interface/macos.py @@ -1,742 +1,12 @@ -import asyncio -import json -import time -from typing import Any, Dict, List, Optional, Tuple -from PIL import Image +from .generic import GenericComputerInterface +from typing import Optional -import websockets - -from ..logger import Logger, LogLevel -from .base import BaseComputerInterface -from ..utils import decode_base64_image, encode_base64_image, bytes_to_image, draw_box, resize_image -from .models import Key, KeyType, MouseButton, CommandResult - -class MacOSComputerInterface(BaseComputerInterface): +class MacOSComputerInterface(GenericComputerInterface): """Interface for macOS.""" def __init__(self, ip_address: str, username: str = "lume", password: str = "lume", api_key: Optional[str] = None, vm_name: Optional[str] = None): - super().__init__(ip_address, username, password, api_key, vm_name) - self._ws = None - self._reconnect_task = None - self._closed = False - self._last_ping = 0 - self._ping_interval = 5 # Send ping every 5 seconds - self._ping_timeout = 120 # Wait 120 seconds for pong response - self._reconnect_delay = 1 # Start with 1 second delay - self._max_reconnect_delay = 30 # Maximum delay between reconnection attempts - self._log_connection_attempts = True # Flag to control connection attempt logging - self._command_lock = asyncio.Lock() # Lock to ensure only one command at a time - - # Set logger name for macOS interface - self.logger = Logger("computer.interface.macos", LogLevel.NORMAL) - - @property - def ws_uri(self) -> str: - """Get the WebSocket URI using the current IP address. - - Returns: - WebSocket URI for the Computer API Server - """ - protocol = "wss" if self.api_key else "ws" - port = "8443" if self.api_key else "8000" - return f"{protocol}://{self.ip_address}:{port}/ws" - - async def _keep_alive(self): - """Keep the WebSocket connection alive with automatic reconnection.""" - retry_count = 0 - max_log_attempts = 1 # Only log the first attempt at INFO level - log_interval = 500 # Then log every 500th attempt (significantly increased from 30) - last_warning_time = 0 - min_warning_interval = 30 # Minimum seconds between connection lost warnings - min_retry_delay = 0.5 # Minimum delay between connection attempts (500ms) - - while not self._closed: - try: - if self._ws is None or ( - self._ws and self._ws.state == websockets.protocol.State.CLOSED - ): - try: - retry_count += 1 - - # Add a minimum delay between connection attempts to avoid flooding - if retry_count > 1: - await asyncio.sleep(min_retry_delay) - - # Only log the first attempt at INFO level, then every Nth attempt - if retry_count == 1: - self.logger.info(f"Attempting WebSocket connection to {self.ws_uri}") - elif retry_count % log_interval == 0: - self.logger.info( - f"Still attempting WebSocket connection (attempt {retry_count})..." - ) - else: - # All other attempts are logged at DEBUG level - self.logger.debug( - f"Attempting WebSocket connection to {self.ws_uri} (attempt {retry_count})" - ) - - self._ws = await asyncio.wait_for( - websockets.connect( - self.ws_uri, - max_size=1024 * 1024 * 10, # 10MB limit - max_queue=32, - ping_interval=self._ping_interval, - ping_timeout=self._ping_timeout, - close_timeout=5, - compression=None, # Disable compression to reduce overhead - ), - timeout=120, - ) - self.logger.info("WebSocket connection established") - - # If api_key and vm_name are provided, perform authentication handshake - if self.api_key and self.vm_name: - self.logger.info("Performing authentication handshake...") - auth_message = { - "command": "authenticate", - "params": { - "api_key": self.api_key, - "container_name": self.vm_name - } - } - await self._ws.send(json.dumps(auth_message)) - - # Wait for authentication response - auth_response = await asyncio.wait_for(self._ws.recv(), timeout=10) - auth_result = json.loads(auth_response) - - if not auth_result.get("success"): - error_msg = auth_result.get("error", "Authentication failed") - self.logger.error(f"Authentication failed: {error_msg}") - await self._ws.close() - self._ws = None - raise ConnectionError(f"Authentication failed: {error_msg}") - - self.logger.info("Authentication successful") - - self._reconnect_delay = 1 # Reset reconnect delay on successful connection - self._last_ping = time.time() - retry_count = 0 # Reset retry count on successful connection - except (asyncio.TimeoutError, websockets.exceptions.WebSocketException) as e: - next_retry = self._reconnect_delay - - # Only log the first error at WARNING level, then every Nth attempt - if retry_count == 1: - self.logger.warning( - f"Computer API Server not ready yet. Will retry automatically." - ) - elif retry_count % log_interval == 0: - self.logger.warning( - f"Still waiting for Computer API Server (attempt {retry_count})..." - ) - else: - # All other errors are logged at DEBUG level - self.logger.debug(f"Connection attempt {retry_count} failed: {e}") - - if self._ws: - try: - await self._ws.close() - except: - pass - self._ws = None - - # Use exponential backoff for connection retries - await asyncio.sleep(self._reconnect_delay) - self._reconnect_delay = min( - self._reconnect_delay * 2, self._max_reconnect_delay - ) - continue - - # Regular ping to check connection - if self._ws and self._ws.state == websockets.protocol.State.OPEN: - try: - if time.time() - self._last_ping >= self._ping_interval: - pong_waiter = await self._ws.ping() - await asyncio.wait_for(pong_waiter, timeout=self._ping_timeout) - self._last_ping = time.time() - except Exception as e: - self.logger.debug(f"Ping failed: {e}") - if self._ws: - try: - await self._ws.close() - except: - pass - self._ws = None - continue - - await asyncio.sleep(1) - - except Exception as e: - current_time = time.time() - # Only log connection lost warnings at most once every min_warning_interval seconds - if current_time - last_warning_time >= min_warning_interval: - self.logger.warning( - f"Computer API Server connection lost. Will retry automatically." - ) - last_warning_time = current_time - else: - # Log at debug level instead - self.logger.debug(f"Connection lost: {e}") - - if self._ws: - try: - await self._ws.close() - except: - pass - self._ws = None - - async def _ensure_connection(self): - """Ensure WebSocket connection is established.""" - if self._reconnect_task is None or self._reconnect_task.done(): - self._reconnect_task = asyncio.create_task(self._keep_alive()) - - retry_count = 0 - max_retries = 5 - - while retry_count < max_retries: - try: - if self._ws and self._ws.state == websockets.protocol.State.OPEN: - return - retry_count += 1 - await asyncio.sleep(1) - except Exception as e: - # Only log at ERROR level for the last retry attempt - if retry_count == max_retries - 1: - self.logger.error( - f"Persistent connection check error after {retry_count} attempts: {e}" - ) - else: - self.logger.debug(f"Connection check error (attempt {retry_count}): {e}") - retry_count += 1 - await asyncio.sleep(1) - continue - - raise ConnectionError("Failed to establish WebSocket connection after multiple retries") - - async def _send_command(self, command: str, params: Optional[Dict] = None) -> Dict[str, Any]: - """Send command through WebSocket.""" - max_retries = 3 - retry_count = 0 - last_error = None - - # Acquire lock to ensure only one command is processed at a time - async with self._command_lock: - self.logger.debug(f"Acquired lock for command: {command}") - while retry_count < max_retries: - try: - await self._ensure_connection() - if not self._ws: - raise ConnectionError("WebSocket connection is not established") - - message = {"command": command, "params": params or {}} - await self._ws.send(json.dumps(message)) - response = await asyncio.wait_for(self._ws.recv(), timeout=120) - self.logger.debug(f"Completed command: {command}") - return json.loads(response) - except Exception as e: - last_error = e - retry_count += 1 - if retry_count < max_retries: - # Only log at debug level for intermediate retries - self.logger.debug( - f"Command '{command}' failed (attempt {retry_count}/{max_retries}): {e}" - ) - await asyncio.sleep(1) - continue - else: - # Only log at error level for the final failure - self.logger.error( - f"Failed to send command '{command}' after {max_retries} retries" - ) - self.logger.debug(f"Command failure details: {e}") - raise - - raise last_error if last_error else RuntimeError("Failed to send command") - - async def wait_for_ready(self, timeout: int = 60, interval: float = 1.0): - """Wait for WebSocket connection to become available.""" - start_time = time.time() - last_error = None - attempt_count = 0 - progress_interval = 10 # Log progress every 10 seconds - last_progress_time = start_time - - # Disable detailed logging for connection attempts - self._log_connection_attempts = False - - try: - self.logger.info( - f"Waiting for Computer API Server to be ready (timeout: {timeout}s)..." - ) - - # Start the keep-alive task if it's not already running - if self._reconnect_task is None or self._reconnect_task.done(): - self._reconnect_task = asyncio.create_task(self._keep_alive()) - - # Wait for the connection to be established - while time.time() - start_time < timeout: - try: - attempt_count += 1 - current_time = time.time() - - # Log progress periodically without flooding logs - if current_time - last_progress_time >= progress_interval: - elapsed = current_time - start_time - self.logger.info( - f"Still waiting for Computer API Server... (elapsed: {elapsed:.1f}s, attempts: {attempt_count})" - ) - last_progress_time = current_time - - # Check if we have a connection - if self._ws and self._ws.state == websockets.protocol.State.OPEN: - # Test the connection with a simple command - try: - await self._send_command("get_screen_size") - elapsed = time.time() - start_time - self.logger.info( - f"Computer API Server is ready (after {elapsed:.1f}s, {attempt_count} attempts)" - ) - return # Connection is fully working - except Exception as e: - last_error = e - self.logger.debug(f"Connection test failed: {e}") - - # Wait before trying again - await asyncio.sleep(interval) - - except Exception as e: - last_error = e - self.logger.debug(f"Connection attempt {attempt_count} failed: {e}") - await asyncio.sleep(interval) - - # If we get here, we've timed out - error_msg = f"Could not connect to {self.ip_address} after {timeout} seconds" - if last_error: - error_msg += f": {str(last_error)}" - self.logger.error(error_msg) - raise TimeoutError(error_msg) - finally: - # Reset to default logging behavior - self._log_connection_attempts = False - - def close(self): - """Close WebSocket connection. - - Note: In host computer server mode, we leave the connection open - to allow other clients to connect to the same server. The server - will handle cleaning up idle connections. - """ - # Only cancel the reconnect task - if self._reconnect_task: - self._reconnect_task.cancel() - - # Don't set closed flag or close websocket by default - # This allows the server to stay connected for other clients - # self._closed = True - # if self._ws: - # asyncio.create_task(self._ws.close()) - # self._ws = None - - def force_close(self): - """Force close the WebSocket connection. - - This method should be called when you want to completely - shut down the connection, not just for regular cleanup. - """ - self._closed = True - if self._reconnect_task: - self._reconnect_task.cancel() - if self._ws: - asyncio.create_task(self._ws.close()) - self._ws = None + super().__init__(ip_address, username, password, api_key, vm_name, "computer.interface.macos") async def diorama_cmd(self, action: str, arguments: Optional[dict] = None) -> dict: """Send a diorama command to the server (macOS only).""" - return await self._send_command("diorama_cmd", {"action": action, "arguments": arguments or {}}) - - # Mouse Actions - async def mouse_down(self, x: Optional[int] = None, y: Optional[int] = None, button: "MouseButton" = "left") -> None: - await self._send_command("mouse_down", {"x": x, "y": y, "button": button}) - - async def mouse_up(self, x: Optional[int] = None, y: Optional[int] = None, button: "MouseButton" = "left") -> None: - await self._send_command("mouse_up", {"x": x, "y": y, "button": button}) - - async def left_click(self, x: Optional[int] = None, y: Optional[int] = None) -> None: - await self._send_command("left_click", {"x": x, "y": y}) - - async def right_click(self, x: Optional[int] = None, y: Optional[int] = None) -> None: - await self._send_command("right_click", {"x": x, "y": y}) - - async def double_click(self, x: Optional[int] = None, y: Optional[int] = None) -> None: - await self._send_command("double_click", {"x": x, "y": y}) - - async def move_cursor(self, x: int, y: int) -> None: - await self._send_command("move_cursor", {"x": x, "y": y}) - - async def drag_to(self, x: int, y: int, button: str = "left", duration: float = 0.5) -> None: - await self._send_command( - "drag_to", {"x": x, "y": y, "button": button, "duration": duration} - ) - - async def drag(self, path: List[Tuple[int, int]], button: str = "left", duration: float = 0.5) -> None: - await self._send_command( - "drag", {"path": path, "button": button, "duration": duration} - ) - - # Keyboard Actions - async def key_down(self, key: "KeyType") -> None: - await self._send_command("key_down", {"key": key}) - - async def key_up(self, key: "KeyType") -> None: - await self._send_command("key_up", {"key": key}) - - async def type_text(self, text: str) -> None: - # Temporary fix for https://github.com/trycua/cua/issues/165 - # Check if text contains Unicode characters - if any(ord(char) > 127 for char in text): - # For Unicode text, use clipboard and paste - await self.set_clipboard(text) - await self.hotkey(Key.COMMAND, 'v') - else: - # For ASCII text, use the regular typing method - await self._send_command("type_text", {"text": text}) - - async def press(self, key: "KeyType") -> None: - """Press a single key. - - Args: - key: The key to press. Can be any of: - - A Key enum value (recommended), e.g. Key.PAGE_DOWN - - A direct key value string, e.g. 'pagedown' - - A single character string, e.g. 'a' - - Examples: - ```python - # Using enum (recommended) - await interface.press(Key.PAGE_DOWN) - await interface.press(Key.ENTER) - - # Using direct values - await interface.press('pagedown') - await interface.press('enter') - - # Using single characters - await interface.press('a') - ``` - - Raises: - ValueError: If the key type is invalid or the key is not recognized - """ - if isinstance(key, Key): - actual_key = key.value - elif isinstance(key, str): - # Try to convert to enum if it matches a known key - key_or_enum = Key.from_string(key) - actual_key = key_or_enum.value if isinstance(key_or_enum, Key) else key_or_enum - else: - raise ValueError(f"Invalid key type: {type(key)}. Must be Key enum or string.") - - await self._send_command("press_key", {"key": actual_key}) - - async def press_key(self, key: "KeyType") -> None: - """DEPRECATED: Use press() instead. - - This method is kept for backward compatibility but will be removed in a future version. - Please use the press() method instead. - """ - await self.press(key) - - async def hotkey(self, *keys: "KeyType") -> None: - """Press multiple keys simultaneously. - - Args: - *keys: Multiple keys to press simultaneously. Each key can be any of: - - A Key enum value (recommended), e.g. Key.COMMAND - - A direct key value string, e.g. 'command' - - A single character string, e.g. 'a' - - Examples: - ```python - # Using enums (recommended) - await interface.hotkey(Key.COMMAND, Key.C) # Copy - await interface.hotkey(Key.COMMAND, Key.V) # Paste - - # Using mixed formats - await interface.hotkey(Key.COMMAND, 'a') # Select all - ``` - - Raises: - ValueError: If any key type is invalid or not recognized - """ - actual_keys = [] - for key in keys: - if isinstance(key, Key): - actual_keys.append(key.value) - elif isinstance(key, str): - # Try to convert to enum if it matches a known key - key_or_enum = Key.from_string(key) - actual_keys.append(key_or_enum.value if isinstance(key_or_enum, Key) else key_or_enum) - else: - raise ValueError(f"Invalid key type: {type(key)}. Must be Key enum or string.") - - await self._send_command("hotkey", {"keys": actual_keys}) - - # Scrolling Actions - async def scroll(self, x: int, y: int) -> None: - await self._send_command("scroll", {"x": x, "y": y}) - - async def scroll_down(self, clicks: int = 1) -> None: - await self._send_command("scroll_down", {"clicks": clicks}) - - async def scroll_up(self, clicks: int = 1) -> None: - await self._send_command("scroll_up", {"clicks": clicks}) - - # Screen Actions - async def screenshot( - self, - boxes: Optional[List[Tuple[int, int, int, int]]] = None, - box_color: str = "#FF0000", - box_thickness: int = 2, - scale_factor: float = 1.0, - ) -> bytes: - """Take a screenshot with optional box drawing and scaling. - - Args: - boxes: Optional list of (x, y, width, height) tuples defining boxes to draw in screen coordinates - box_color: Color of the boxes in hex format (default: "#FF0000" red) - box_thickness: Thickness of the box borders in pixels (default: 2) - scale_factor: Factor to scale the final image by (default: 1.0) - Use > 1.0 to enlarge, < 1.0 to shrink (e.g., 0.5 for half size, 2.0 for double) - - Returns: - bytes: The screenshot image data, optionally with boxes drawn on it and scaled - """ - result = await self._send_command("screenshot") - if not result.get("image_data"): - raise RuntimeError("Failed to take screenshot") - - screenshot = decode_base64_image(result["image_data"]) - - if boxes: - # Get the natural scaling between screen and screenshot - screen_size = await self.get_screen_size() - screenshot_width, screenshot_height = bytes_to_image(screenshot).size - width_scale = screenshot_width / screen_size["width"] - height_scale = screenshot_height / screen_size["height"] - - # Scale box coordinates from screen space to screenshot space - for box in boxes: - scaled_box = ( - int(box[0] * width_scale), # x - int(box[1] * height_scale), # y - int(box[2] * width_scale), # width - int(box[3] * height_scale), # height - ) - screenshot = draw_box( - screenshot, - x=scaled_box[0], - y=scaled_box[1], - width=scaled_box[2], - height=scaled_box[3], - color=box_color, - thickness=box_thickness, - ) - - if scale_factor != 1.0: - screenshot = resize_image(screenshot, scale_factor) - - return screenshot - - async def get_screen_size(self) -> Dict[str, int]: - result = await self._send_command("get_screen_size") - if result["success"] and result["size"]: - return result["size"] - raise RuntimeError("Failed to get screen size") - - async def get_cursor_position(self) -> Dict[str, int]: - result = await self._send_command("get_cursor_position") - if result["success"] and result["position"]: - return result["position"] - raise RuntimeError("Failed to get cursor position") - - # Clipboard Actions - async def copy_to_clipboard(self) -> str: - result = await self._send_command("copy_to_clipboard") - if result["success"] and result["content"]: - return result["content"] - raise RuntimeError("Failed to get clipboard content") - - async def set_clipboard(self, text: str) -> None: - await self._send_command("set_clipboard", {"text": text}) - - # File System Actions - async def file_exists(self, path: str) -> bool: - result = await self._send_command("file_exists", {"path": path}) - return result.get("exists", False) - - async def directory_exists(self, path: str) -> bool: - result = await self._send_command("directory_exists", {"path": path}) - return result.get("exists", False) - - async def list_dir(self, path: str) -> list[str]: - result = await self._send_command("list_dir", {"path": path}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to list directory")) - return result.get("files", []) - - async def read_text(self, path: str) -> str: - result = await self._send_command("read_text", {"path": path}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to read file")) - return result.get("content", "") - - async def write_text(self, path: str, content: str) -> None: - result = await self._send_command("write_text", {"path": path, "content": content}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to write file")) - - async def read_bytes(self, path: str, offset: int = 0, length: Optional[int] = None) -> bytes: - # For large files, use chunked reading - if length is None: - # Get file size first to determine if we need chunking - file_size = await self.get_file_size(path) - # If file is larger than 5MB, read in chunks - if file_size > 5 * 1024 * 1024: # 5MB threshold - return await self._read_bytes_chunked(path, offset, file_size - offset if offset > 0 else file_size) - - result = await self._send_command("read_bytes", { - "path": path, - "offset": offset, - "length": length - }) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to read file")) - content_b64 = result.get("content_b64", "") - return decode_base64_image(content_b64) - - async def get_file_size(self, path: str) -> int: - result = await self._send_command("get_file_size", {"path": path}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to get file size")) - return result.get("size", 0) - - async def _read_bytes_chunked(self, path: str, offset: int, total_length: int, chunk_size: int = 1024 * 1024) -> bytes: - """Read large files in chunks to avoid memory issues.""" - chunks = [] - current_offset = offset - remaining = total_length - - while remaining > 0: - read_size = min(chunk_size, remaining) - result = await self._send_command("read_bytes", { - "path": path, - "offset": current_offset, - "length": read_size - }) - - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to read file chunk")) - - content_b64 = result.get("content_b64", "") - chunk_data = decode_base64_image(content_b64) - chunks.append(chunk_data) - - current_offset += read_size - remaining -= read_size - - return b''.join(chunks) - - async def write_bytes(self, path: str, content: bytes) -> None: - result = await self._send_command("write_bytes", {"path": path, "content_b64": encode_base64_image(content)}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to write file")) - - async def delete_file(self, path: str) -> None: - result = await self._send_command("delete_file", {"path": path}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to delete file")) - - async def create_dir(self, path: str) -> None: - result = await self._send_command("create_dir", {"path": path}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to create directory")) - - async def delete_dir(self, path: str) -> None: - result = await self._send_command("delete_dir", {"path": path}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to delete directory")) - - async def run_command(self, command: str) -> CommandResult: - result = await self._send_command("run_command", {"command": command}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to run command")) - return CommandResult( - stdout=result.get("stdout", ""), - stderr=result.get("stderr", ""), - returncode=result.get("return_code", 0) - ) - - # Accessibility Actions - async def get_accessibility_tree(self) -> Dict[str, Any]: - """Get the accessibility tree of the current screen.""" - result = await self._send_command("get_accessibility_tree") - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to get accessibility tree")) - return result - - async def get_active_window_bounds(self) -> Dict[str, int]: - """Get the bounds of the currently active window.""" - result = await self._send_command("get_active_window_bounds") - if result["success"] and result["bounds"]: - return result["bounds"] - raise RuntimeError("Failed to get active window bounds") - - async def to_screen_coordinates(self, x: float, y: float) -> tuple[float, float]: - """Convert screenshot coordinates to screen coordinates. - - Args: - x: X coordinate in screenshot space - y: Y coordinate in screenshot space - - Returns: - tuple[float, float]: (x, y) coordinates in screen space - """ - screen_size = await self.get_screen_size() - screenshot = await self.screenshot() - screenshot_img = bytes_to_image(screenshot) - screenshot_width, screenshot_height = screenshot_img.size - - # Calculate scaling factors - width_scale = screen_size["width"] / screenshot_width - height_scale = screen_size["height"] / screenshot_height - - # Convert coordinates - screen_x = x * width_scale - screen_y = y * height_scale - - return screen_x, screen_y - - async def to_screenshot_coordinates(self, x: float, y: float) -> tuple[float, float]: - """Convert screen coordinates to screenshot coordinates. - - Args: - x: X coordinate in screen space - y: Y coordinate in screen space - - Returns: - tuple[float, float]: (x, y) coordinates in screenshot space - """ - screen_size = await self.get_screen_size() - screenshot = await self.screenshot() - screenshot_img = bytes_to_image(screenshot) - screenshot_width, screenshot_height = screenshot_img.size - - # Calculate scaling factors - width_scale = screenshot_width / screen_size["width"] - height_scale = screenshot_height / screen_size["height"] - - # Convert coordinates - screenshot_x = x * width_scale - screenshot_y = y * height_scale - - return screenshot_x, screenshot_y + return await self._send_command("diorama_cmd", {"action": action, "arguments": arguments or {}}) \ No newline at end of file diff --git a/libs/python/computer/computer/interface/windows.py b/libs/python/computer/computer/interface/windows.py index b70feb0f..a874d359 100644 --- a/libs/python/computer/computer/interface/windows.py +++ b/libs/python/computer/computer/interface/windows.py @@ -1,734 +1,8 @@ -import asyncio -import json -import time -from typing import Any, Dict, List, Optional, Tuple -from PIL import Image +from typing import Optional +from .generic import GenericComputerInterface -import websockets - -from ..logger import Logger, LogLevel -from .base import BaseComputerInterface -from ..utils import decode_base64_image, encode_base64_image, bytes_to_image, draw_box, resize_image -from .models import Key, KeyType, MouseButton, CommandResult - -class WindowsComputerInterface(BaseComputerInterface): +class WindowsComputerInterface(GenericComputerInterface): """Interface for Windows.""" def __init__(self, ip_address: str, username: str = "lume", password: str = "lume", api_key: Optional[str] = None, vm_name: Optional[str] = None): - super().__init__(ip_address, username, password, api_key, vm_name) - self._ws = None - self._reconnect_task = None - self._closed = False - self._last_ping = 0 - self._ping_interval = 5 # Send ping every 5 seconds - self._ping_timeout = 120 # Wait 120 seconds for pong response - self._reconnect_delay = 1 # Start with 1 second delay - self._max_reconnect_delay = 30 # Maximum delay between reconnection attempts - self._log_connection_attempts = True # Flag to control connection attempt logging - self._authenticated = False # Track authentication status - self._command_lock = asyncio.Lock() # Lock to ensure only one command at a time - - # Set logger name for Windows interface - self.logger = Logger("computer.interface.windows", LogLevel.NORMAL) - - @property - def ws_uri(self) -> str: - """Get the WebSocket URI using the current IP address. - - Returns: - WebSocket URI for the Computer API Server - """ - protocol = "wss" if self.api_key else "ws" - port = "8443" if self.api_key else "8000" - return f"{protocol}://{self.ip_address}:{port}/ws" - - async def _keep_alive(self): - """Keep the WebSocket connection alive with automatic reconnection.""" - retry_count = 0 - max_log_attempts = 1 # Only log the first attempt at INFO level - log_interval = 500 # Then log every 500th attempt (significantly increased from 30) - last_warning_time = 0 - min_warning_interval = 30 # Minimum seconds between connection lost warnings - min_retry_delay = 0.5 # Minimum delay between connection attempts (500ms) - - while not self._closed: - try: - if self._ws is None or ( - self._ws and self._ws.state == websockets.protocol.State.CLOSED - ): - try: - retry_count += 1 - - # Add a minimum delay between connection attempts to avoid flooding - if retry_count > 1: - await asyncio.sleep(min_retry_delay) - - # Only log the first attempt at INFO level, then every Nth attempt - if retry_count == 1: - self.logger.info(f"Attempting WebSocket connection to {self.ws_uri}") - elif retry_count % log_interval == 0: - self.logger.info( - f"Still attempting WebSocket connection (attempt {retry_count})..." - ) - else: - # All other attempts are logged at DEBUG level - self.logger.debug( - f"Attempting WebSocket connection to {self.ws_uri} (attempt {retry_count})" - ) - - self._ws = await asyncio.wait_for( - websockets.connect( - self.ws_uri, - max_size=1024 * 1024 * 10, # 10MB limit - max_queue=32, - ping_interval=self._ping_interval, - ping_timeout=self._ping_timeout, - close_timeout=5, - compression=None, # Disable compression to reduce overhead - ), - timeout=120, - ) - self.logger.info("WebSocket connection established") - - # Authentication will be handled by the first command that needs it - # Don't do authentication here to avoid recv conflicts - - self._reconnect_delay = 1 # Reset reconnect delay on successful connection - self._last_ping = time.time() - retry_count = 0 # Reset retry count on successful connection - self._authenticated = False # Reset auth status on new connection - - except (asyncio.TimeoutError, websockets.exceptions.WebSocketException) as e: - next_retry = self._reconnect_delay - - # Only log the first error at WARNING level, then every Nth attempt - if retry_count == 1: - self.logger.warning( - f"Computer API Server not ready yet. Will retry automatically." - ) - elif retry_count % log_interval == 0: - self.logger.warning( - f"Still waiting for Computer API Server (attempt {retry_count})..." - ) - else: - # All other errors are logged at DEBUG level - self.logger.debug(f"Connection attempt {retry_count} failed: {e}") - - if self._ws: - try: - await self._ws.close() - except: - pass - self._ws = None - - # Regular ping to check connection - if self._ws and self._ws.state == websockets.protocol.State.OPEN: - try: - if time.time() - self._last_ping >= self._ping_interval: - pong_waiter = await self._ws.ping() - await asyncio.wait_for(pong_waiter, timeout=self._ping_timeout) - self._last_ping = time.time() - except Exception as e: - self.logger.debug(f"Ping failed: {e}") - if self._ws: - try: - await self._ws.close() - except: - pass - self._ws = None - continue - - await asyncio.sleep(1) - - except Exception as e: - current_time = time.time() - # Only log connection lost warnings at most once every min_warning_interval seconds - if current_time - last_warning_time >= min_warning_interval: - self.logger.warning( - f"Computer API Server connection lost. Will retry automatically." - ) - last_warning_time = current_time - else: - # Log at debug level instead - self.logger.debug(f"Connection lost: {e}") - - if self._ws: - try: - await self._ws.close() - except: - pass - self._ws = None - - async def _ensure_connection(self): - """Ensure WebSocket connection is established.""" - if self._reconnect_task is None or self._reconnect_task.done(): - self._reconnect_task = asyncio.create_task(self._keep_alive()) - - retry_count = 0 - max_retries = 5 - - while retry_count < max_retries: - try: - if self._ws and self._ws.state == websockets.protocol.State.OPEN: - return - retry_count += 1 - await asyncio.sleep(1) - except Exception as e: - # Only log at ERROR level for the last retry attempt - if retry_count == max_retries - 1: - self.logger.error( - f"Persistent connection check error after {retry_count} attempts: {e}" - ) - else: - self.logger.debug(f"Connection check error (attempt {retry_count}): {e}") - retry_count += 1 - await asyncio.sleep(1) - continue - - raise ConnectionError("Failed to establish WebSocket connection after multiple retries") - - async def _send_command(self, command: str, params: Optional[Dict] = None) -> Dict[str, Any]: - """Send command through WebSocket.""" - max_retries = 3 - retry_count = 0 - last_error = None - - # Acquire lock to ensure only one command is processed at a time - async with self._command_lock: - self.logger.debug(f"Acquired lock for command: {command}") - while retry_count < max_retries: - try: - await self._ensure_connection() - if not self._ws: - raise ConnectionError("WebSocket connection is not established") - - # Handle authentication if needed - if self.api_key and self.vm_name and not self._authenticated: - self.logger.info("Performing authentication handshake...") - auth_message = { - "command": "authenticate", - "params": { - "api_key": self.api_key, - "container_name": self.vm_name - } - } - await self._ws.send(json.dumps(auth_message)) - - # Wait for authentication response - auth_response = await asyncio.wait_for(self._ws.recv(), timeout=10) - auth_result = json.loads(auth_response) - - if not auth_result.get("success"): - error_msg = auth_result.get("error", "Authentication failed") - self.logger.error(f"Authentication failed: {error_msg}") - self._authenticated = False - raise ConnectionError(f"Authentication failed: {error_msg}") - - self.logger.info("Authentication successful") - self._authenticated = True - - message = {"command": command, "params": params or {}} - await self._ws.send(json.dumps(message)) - response = await asyncio.wait_for(self._ws.recv(), timeout=30) - self.logger.debug(f"Completed command: {command}") - return json.loads(response) - except Exception as e: - last_error = e - retry_count += 1 - if retry_count < max_retries: - # Only log at debug level for intermediate retries - self.logger.debug( - f"Command '{command}' failed (attempt {retry_count}/{max_retries}): {e}" - ) - await asyncio.sleep(1) - continue - else: - # Only log at error level for the final failure - self.logger.error( - f"Failed to send command '{command}' after {max_retries} retries" - ) - self.logger.debug(f"Command failure details: {e}") - raise last_error if last_error else RuntimeError("Failed to send command") - - async def wait_for_ready(self, timeout: int = 60, interval: float = 1.0): - """Wait for WebSocket connection to become available.""" - start_time = time.time() - last_error = None - attempt_count = 0 - progress_interval = 10 # Log progress every 10 seconds - last_progress_time = start_time - - # Disable detailed logging for connection attempts - self._log_connection_attempts = False - - try: - self.logger.info( - f"Waiting for Computer API Server to be ready (timeout: {timeout}s)..." - ) - - # Start the keep-alive task if it's not already running - if self._reconnect_task is None or self._reconnect_task.done(): - self._reconnect_task = asyncio.create_task(self._keep_alive()) - - # Wait for the connection to be established - while time.time() - start_time < timeout: - try: - attempt_count += 1 - current_time = time.time() - - # Log progress periodically without flooding logs - if current_time - last_progress_time >= progress_interval: - elapsed = current_time - start_time - self.logger.info( - f"Still waiting for Computer API Server... (elapsed: {elapsed:.1f}s, attempts: {attempt_count})" - ) - last_progress_time = current_time - - # Check if we have a connection - if self._ws and self._ws.state == websockets.protocol.State.OPEN: - # Test the connection with a simple command - try: - await self._send_command("get_screen_size") - elapsed = time.time() - start_time - self.logger.info( - f"Computer API Server is ready (after {elapsed:.1f}s, {attempt_count} attempts)" - ) - return # Connection is fully working - except Exception as e: - last_error = e - self.logger.debug(f"Connection test failed: {e}") - - # Wait before trying again - await asyncio.sleep(interval) - - except Exception as e: - last_error = e - self.logger.debug(f"Connection attempt {attempt_count} failed: {e}") - await asyncio.sleep(interval) - - # If we get here, we've timed out - error_msg = f"Could not connect to {self.ip_address} after {timeout} seconds" - if last_error: - error_msg += f": {str(last_error)}" - self.logger.error(error_msg) - raise TimeoutError(error_msg) - finally: - # Reset to default logging behavior - self._log_connection_attempts = False - - def close(self): - """Close WebSocket connection. - - Note: In host computer server mode, we leave the connection open - to allow other clients to connect to the same server. The server - will handle cleaning up idle connections. - """ - # Only cancel the reconnect task - if self._reconnect_task: - self._reconnect_task.cancel() - - # Don't set closed flag or close websocket by default - # This allows the server to stay connected for other clients - # self._closed = True - # if self._ws: - # asyncio.create_task(self._ws.close()) - # self._ws = None - - def force_close(self): - """Force close the WebSocket connection. - - This method should be called when you want to completely - shut down the connection, not just for regular cleanup. - """ - self._closed = True - if self._reconnect_task: - self._reconnect_task.cancel() - if self._ws: - asyncio.create_task(self._ws.close()) - self._ws = None - - # Mouse Actions - async def mouse_down(self, x: Optional[int] = None, y: Optional[int] = None, button: str = "left") -> None: - await self._send_command("mouse_down", {"x": x, "y": y, "button": button}) - - async def mouse_up(self, x: Optional[int] = None, y: Optional[int] = None, button: str = "left") -> None: - await self._send_command("mouse_up", {"x": x, "y": y, "button": button}) - - async def left_click(self, x: Optional[int] = None, y: Optional[int] = None) -> None: - await self._send_command("left_click", {"x": x, "y": y}) - - async def right_click(self, x: Optional[int] = None, y: Optional[int] = None) -> None: - await self._send_command("right_click", {"x": x, "y": y}) - - async def double_click(self, x: Optional[int] = None, y: Optional[int] = None) -> None: - await self._send_command("double_click", {"x": x, "y": y}) - - async def move_cursor(self, x: int, y: int) -> None: - await self._send_command("move_cursor", {"x": x, "y": y}) - - async def drag_to(self, x: int, y: int, button: "MouseButton" = "left", duration: float = 0.5) -> None: - await self._send_command( - "drag_to", {"x": x, "y": y, "button": button, "duration": duration} - ) - - async def drag(self, path: List[Tuple[int, int]], button: "MouseButton" = "left", duration: float = 0.5) -> None: - await self._send_command( - "drag", {"path": path, "button": button, "duration": duration} - ) - - # Keyboard Actions - async def key_down(self, key: "KeyType") -> None: - await self._send_command("key_down", {"key": key}) - - async def key_up(self, key: "KeyType") -> None: - await self._send_command("key_up", {"key": key}) - - async def type_text(self, text: str) -> None: - # For Windows, use clipboard for Unicode text like Linux - if any(ord(char) > 127 for char in text): - # For Unicode text, use clipboard and paste - await self.set_clipboard(text) - await self.hotkey(Key.CTRL, 'v') # Windows uses Ctrl+V instead of Cmd+V - else: - # For ASCII text, use the regular typing method - await self._send_command("type_text", {"text": text}) - - async def press(self, key: "KeyType") -> None: - """Press a single key. - - Args: - key: The key to press. Can be any of: - - A Key enum value (recommended), e.g. Key.PAGE_DOWN - - A direct key value string, e.g. 'pagedown' - - A single character string, e.g. 'a' - - Examples: - ```python - # Using enum (recommended) - await interface.press(Key.PAGE_DOWN) - await interface.press(Key.ENTER) - - # Using direct values - await interface.press('pagedown') - await interface.press('enter') - - # Using single characters - await interface.press('a') - ``` - - Raises: - ValueError: If the key type is invalid or the key is not recognized - """ - if isinstance(key, Key): - actual_key = key.value - elif isinstance(key, str): - # Try to convert to enum if it matches a known key - key_or_enum = Key.from_string(key) - actual_key = key_or_enum.value if isinstance(key_or_enum, Key) else key_or_enum - else: - raise ValueError(f"Invalid key type: {type(key)}. Must be Key enum or string.") - - await self._send_command("press_key", {"key": actual_key}) - - async def press_key(self, key: "KeyType") -> None: - """DEPRECATED: Use press() instead. - - This method is kept for backward compatibility but will be removed in a future version. - Please use the press() method instead. - """ - await self.press(key) - - async def hotkey(self, *keys: "KeyType") -> None: - """Press multiple keys simultaneously. - - Args: - *keys: Multiple keys to press simultaneously. Each key can be any of: - - A Key enum value (recommended), e.g. Key.CTRL - - A direct key value string, e.g. 'ctrl' - - A single character string, e.g. 'a' - - Examples: - ```python - # Using enums (recommended) - await interface.hotkey(Key.CTRL, Key.C) # Copy - await interface.hotkey(Key.CTRL, Key.V) # Paste - - # Using mixed formats - await interface.hotkey(Key.CTRL, 'a') # Select all - ``` - - Raises: - ValueError: If any key type is invalid or not recognized - """ - actual_keys = [] - for key in keys: - if isinstance(key, Key): - actual_keys.append(key.value) - elif isinstance(key, str): - # Try to convert to enum if it matches a known key - key_or_enum = Key.from_string(key) - actual_keys.append(key_or_enum.value if isinstance(key_or_enum, Key) else key_or_enum) - else: - raise ValueError(f"Invalid key type: {type(key)}. Must be Key enum or string.") - - await self._send_command("hotkey", {"keys": actual_keys}) - - # Scrolling Actions - async def scroll(self, x: int, y: int) -> None: - await self._send_command("scroll", {"x": x, "y": y}) - - async def scroll_down(self, clicks: int = 1) -> None: - await self._send_command("scroll_down", {"clicks": clicks}) - - async def scroll_up(self, clicks: int = 1) -> None: - await self._send_command("scroll_up", {"clicks": clicks}) - - # Screen Actions - async def screenshot( - self, - boxes: Optional[List[Tuple[int, int, int, int]]] = None, - box_color: str = "#FF0000", - box_thickness: int = 2, - scale_factor: float = 1.0, - ) -> bytes: - """Take a screenshot with optional box drawing and scaling. - - Args: - boxes: Optional list of (x, y, width, height) tuples defining boxes to draw in screen coordinates - box_color: Color of the boxes in hex format (default: "#FF0000" red) - box_thickness: Thickness of the box borders in pixels (default: 2) - scale_factor: Factor to scale the final image by (default: 1.0) - Use > 1.0 to enlarge, < 1.0 to shrink (e.g., 0.5 for half size, 2.0 for double) - - Returns: - bytes: The screenshot image data, optionally with boxes drawn on it and scaled - """ - result = await self._send_command("screenshot") - if not result.get("image_data"): - raise RuntimeError("Failed to take screenshot") - - screenshot = decode_base64_image(result["image_data"]) - - if boxes: - # Get the natural scaling between screen and screenshot - screen_size = await self.get_screen_size() - screenshot_width, screenshot_height = bytes_to_image(screenshot).size - width_scale = screenshot_width / screen_size["width"] - height_scale = screenshot_height / screen_size["height"] - - # Scale box coordinates from screen space to screenshot space - for box in boxes: - scaled_box = ( - int(box[0] * width_scale), # x - int(box[1] * height_scale), # y - int(box[2] * width_scale), # width - int(box[3] * height_scale), # height - ) - screenshot = draw_box( - screenshot, - x=scaled_box[0], - y=scaled_box[1], - width=scaled_box[2], - height=scaled_box[3], - color=box_color, - thickness=box_thickness, - ) - - if scale_factor != 1.0: - screenshot = resize_image(screenshot, scale_factor) - - return screenshot - - async def get_screen_size(self) -> Dict[str, int]: - result = await self._send_command("get_screen_size") - if result["success"] and result["size"]: - return result["size"] - raise RuntimeError("Failed to get screen size") - - async def get_cursor_position(self) -> Dict[str, int]: - result = await self._send_command("get_cursor_position") - if result["success"] and result["position"]: - return result["position"] - raise RuntimeError("Failed to get cursor position") - - # Clipboard Actions - async def copy_to_clipboard(self) -> str: - result = await self._send_command("copy_to_clipboard") - if result["success"] and result["content"]: - return result["content"] - raise RuntimeError("Failed to get clipboard content") - - async def set_clipboard(self, text: str) -> None: - await self._send_command("set_clipboard", {"text": text}) - - # File System Actions - async def file_exists(self, path: str) -> bool: - result = await self._send_command("file_exists", {"path": path}) - return result.get("exists", False) - - async def directory_exists(self, path: str) -> bool: - result = await self._send_command("directory_exists", {"path": path}) - return result.get("exists", False) - - async def list_dir(self, path: str) -> list[str]: - result = await self._send_command("list_dir", {"path": path}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to list directory")) - return result.get("files", []) - - async def read_text(self, path: str) -> str: - result = await self._send_command("read_text", {"path": path}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to read file")) - return result.get("content", "") - - async def write_text(self, path: str, content: str) -> None: - result = await self._send_command("write_text", {"path": path, "content": content}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to write file")) - - async def read_bytes(self, path: str, offset: int = 0, length: Optional[int] = None) -> bytes: - # For large files, use chunked reading - if length is None: - # Get file size first to determine if we need chunking - file_size = await self.get_file_size(path) - # If file is larger than 5MB, read in chunks - if file_size > 5 * 1024 * 1024: # 5MB threshold - return await self._read_bytes_chunked(path, offset, file_size - offset if offset > 0 else file_size) - - result = await self._send_command("read_bytes", { - "path": path, - "offset": offset, - "length": length - }) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to read file")) - content_b64 = result.get("content_b64", "") - return decode_base64_image(content_b64) - - async def get_file_size(self, path: str) -> int: - result = await self._send_command("get_file_size", {"path": path}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to get file size")) - return result.get("size", 0) - - async def _read_bytes_chunked(self, path: str, offset: int, total_length: int, chunk_size: int = 1024 * 1024) -> bytes: - """Read large files in chunks to avoid memory issues.""" - chunks = [] - current_offset = offset - remaining = total_length - - while remaining > 0: - read_size = min(chunk_size, remaining) - result = await self._send_command("read_bytes", { - "path": path, - "offset": current_offset, - "length": read_size - }) - - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to read file chunk")) - - content_b64 = result.get("content_b64", "") - chunk_data = decode_base64_image(content_b64) - chunks.append(chunk_data) - - current_offset += read_size - remaining -= read_size - - return b''.join(chunks) - - async def write_bytes(self, path: str, content: bytes) -> None: - result = await self._send_command("write_bytes", {"path": path, "content_b64": encode_base64_image(content)}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to write file")) - - async def delete_file(self, path: str) -> None: - result = await self._send_command("delete_file", {"path": path}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to delete file")) - - async def create_dir(self, path: str) -> None: - result = await self._send_command("create_dir", {"path": path}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to create directory")) - - async def delete_dir(self, path: str) -> None: - result = await self._send_command("delete_dir", {"path": path}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to delete directory")) - - async def run_command(self, command: str) -> CommandResult: - result = await self._send_command("run_command", {"command": command}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to run command")) - return CommandResult( - stdout=result.get("stdout", ""), - stderr=result.get("stderr", ""), - returncode=result.get("return_code", 0) - ) - - # Accessibility Actions - async def get_accessibility_tree(self) -> Dict[str, Any]: - """Get the accessibility tree of the current screen.""" - result = await self._send_command("get_accessibility_tree") - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to get accessibility tree")) - return result - - async def get_active_window_bounds(self) -> Dict[str, int]: - """Get the bounds of the currently active window.""" - result = await self._send_command("get_active_window_bounds") - if result["success"] and result["bounds"]: - return result["bounds"] - raise RuntimeError("Failed to get active window bounds") - - async def to_screen_coordinates(self, x: float, y: float) -> tuple[float, float]: - """Convert screenshot coordinates to screen coordinates. - - Args: - x: X coordinate in screenshot space - y: Y coordinate in screenshot space - - Returns: - tuple[float, float]: (x, y) coordinates in screen space - """ - screen_size = await self.get_screen_size() - screenshot = await self.screenshot() - screenshot_img = bytes_to_image(screenshot) - screenshot_width, screenshot_height = screenshot_img.size - - # Calculate scaling factors - width_scale = screen_size["width"] / screenshot_width - height_scale = screen_size["height"] / screenshot_height - - # Convert coordinates - screen_x = x * width_scale - screen_y = y * height_scale - - return screen_x, screen_y - - async def to_screenshot_coordinates(self, x: float, y: float) -> tuple[float, float]: - """Convert screen coordinates to screenshot coordinates. - - Args: - x: X coordinate in screen space - y: Y coordinate in screen space - - Returns: - tuple[float, float]: (x, y) coordinates in screenshot space - """ - screen_size = await self.get_screen_size() - screenshot = await self.screenshot() - screenshot_img = bytes_to_image(screenshot) - screenshot_width, screenshot_height = screenshot_img.size - - # Calculate scaling factors - width_scale = screenshot_width / screen_size["width"] - height_scale = screenshot_height / screen_size["height"] - - # Convert coordinates - screenshot_x = x * width_scale - screenshot_y = y * height_scale - - return screenshot_x, screenshot_y + super().__init__(ip_address, username, password, api_key, vm_name, "computer.interface.windows") From 3f4a2b76b4a069d39ffa98a9fb5ef090b1e6118f Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 1 Jul 2025 10:58:24 -0400 Subject: [PATCH 117/141] Added append write --- .../computer-server/computer_server/handlers/generic.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libs/python/computer-server/computer_server/handlers/generic.py b/libs/python/computer-server/computer_server/handlers/generic.py index cdf9df7d..03472fbd 100644 --- a/libs/python/computer-server/computer_server/handlers/generic.py +++ b/libs/python/computer-server/computer_server/handlers/generic.py @@ -47,9 +47,11 @@ class GenericFileHandler(BaseFileHandler): except Exception as e: return {"success": False, "error": str(e)} - async def write_bytes(self, path: str, content_b64: str) -> Dict[str, Any]: + async def write_bytes(self, path: str, content_b64: str, append: bool = False) -> Dict[str, Any]: try: - resolve_path(path).write_bytes(base64.b64decode(content_b64)) + mode = 'ab' if append else 'wb' + with open(resolve_path(path), mode) as f: + f.write(base64.b64decode(content_b64)) return {"success": True} except Exception as e: return {"success": False, "error": str(e)} From 25963411266758c3ed44a957e7ffda94645e0a4c Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 1 Jul 2025 10:58:45 -0400 Subject: [PATCH 118/141] Filtered out mismatching kwargs --- libs/python/computer-server/computer_server/main.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libs/python/computer-server/computer_server/main.py b/libs/python/computer-server/computer_server/main.py index de422887..29b19faf 100644 --- a/libs/python/computer-server/computer_server/main.py +++ b/libs/python/computer-server/computer_server/main.py @@ -5,6 +5,7 @@ import logging import asyncio import json import traceback +import inspect from contextlib import redirect_stdout, redirect_stderr from io import StringIO from .handlers.factory import HandlerFactory @@ -218,7 +219,12 @@ async def websocket_endpoint(websocket: WebSocket): continue try: - result = await handlers[command](**params) + # Filter params to only include those accepted by the handler function + handler_func = handlers[command] + sig = inspect.signature(handler_func) + filtered_params = {k: v for k, v in params.items() if k in sig.parameters} + + result = await handler_func(**filtered_params) await websocket.send_json({"success": True, **result}) except Exception as cmd_error: logger.error(f"Error executing command {command}: {str(cmd_error)}") From 684ca0f273183b08aa6bd71c2531bb78255c5e61 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 1 Jul 2025 11:08:45 -0400 Subject: [PATCH 119/141] Added read/write text encoding and large text support --- .../computer/computer/interface/generic.py | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/libs/python/computer/computer/interface/generic.py b/libs/python/computer/computer/interface/generic.py index 46c949c3..a3521816 100644 --- a/libs/python/computer/computer/interface/generic.py +++ b/libs/python/computer/computer/interface/generic.py @@ -337,16 +337,30 @@ class GenericComputerInterface(BaseComputerInterface): content_b64 = result.get("content_b64", "") return decode_base64_image(content_b64) - async def read_text(self, path: str) -> str: - result = await self._send_command("read_text", {"path": path}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to read file")) - return result.get("content", "") + async def read_text(self, path: str, encoding: str = 'utf-8') -> str: + """Read text from a file with specified encoding. + + Args: + path: Path to the file to read + encoding: Text encoding to use (default: 'utf-8') + + Returns: + str: The decoded text content of the file + """ + content_bytes = await self.read_bytes(path) + return content_bytes.decode(encoding) - async def write_text(self, path: str, content: str) -> None: - result = await self._send_command("write_text", {"path": path, "content": content}) - if not result.get("success", False): - raise RuntimeError(result.get("error", "Failed to write file")) + async def write_text(self, path: str, content: str, encoding: str = 'utf-8', append: bool = False) -> None: + """Write text to a file with specified encoding. + + Args: + path: Path to the file to write + content: Text content to write + encoding: Text encoding to use (default: 'utf-8') + append: Whether to append to the file instead of overwriting + """ + content_bytes = content.encode(encoding) + await self.write_bytes(path, content_bytes, append) async def get_file_size(self, path: str) -> int: result = await self._send_command("get_file_size", {"path": path}) From 9b205d2a762bb3a4fa09deac36e89d2409f491fb Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 1 Jul 2025 11:13:47 -0400 Subject: [PATCH 120/141] Updated TS commands --- .../computer/src/interface/macos.ts | 134 ++++++++++++++++-- 1 file changed, 123 insertions(+), 11 deletions(-) diff --git a/libs/typescript/computer/src/interface/macos.ts b/libs/typescript/computer/src/interface/macos.ts index 3c41e03f..7f7383a0 100644 --- a/libs/typescript/computer/src/interface/macos.ts +++ b/libs/typescript/computer/src/interface/macos.ts @@ -148,33 +148,145 @@ export class MacOSComputerInterface extends BaseComputerInterface { return (response.files as string[]) || []; } - async readText(path: string): Promise { - const response = await this.sendCommand('read_text', { path }); + async getFileSize(path: string): Promise { + const response = await this.sendCommand('get_file_size', { path }); if (!response.success) { - throw new Error((response.error as string) || 'Failed to read file'); + throw new Error((response.error as string) || 'Failed to get file size'); } - return (response.content as string) || ''; + return (response.size as number) || 0; } - async writeText(path: string, content: string): Promise { - const response = await this.sendCommand('write_text', { path, content }); - if (!response.success) { - throw new Error((response.error as string) || 'Failed to write file'); + private async readBytesChunked( + path: string, + offset: number, + totalLength: number, + chunkSize: number = 1024 * 1024 + ): Promise { + const chunks: Buffer[] = []; + let currentOffset = offset; + let remaining = totalLength; + + while (remaining > 0) { + const readSize = Math.min(chunkSize, remaining); + const response = await this.sendCommand('read_bytes', { + path, + offset: currentOffset, + length: readSize, + }); + + if (!response.success) { + throw new Error( + (response.error as string) || 'Failed to read file chunk' + ); + } + + const chunkData = Buffer.from(response.content_b64 as string, 'base64'); + chunks.push(chunkData); + + currentOffset += readSize; + remaining -= readSize; + } + + return Buffer.concat(chunks); + } + + private async writeBytesChunked( + path: string, + content: Buffer, + append: boolean = false, + chunkSize: number = 1024 * 1024 + ): Promise { + const totalSize = content.length; + let currentOffset = 0; + + while (currentOffset < totalSize) { + const chunkEnd = Math.min(currentOffset + chunkSize, totalSize); + const chunkData = content.subarray(currentOffset, chunkEnd); + + // First chunk uses the original append flag, subsequent chunks always append + const chunkAppend = currentOffset === 0 ? append : true; + + const response = await this.sendCommand('write_bytes', { + path, + content_b64: chunkData.toString('base64'), + append: chunkAppend, + }); + + if (!response.success) { + throw new Error( + (response.error as string) || 'Failed to write file chunk' + ); + } + + currentOffset = chunkEnd; } } - async readBytes(path: string): Promise { - const response = await this.sendCommand('read_bytes', { path }); + async readText(path: string, encoding: BufferEncoding = 'utf8'): Promise { + /** + * Read text from a file with specified encoding. + * + * @param path - Path to the file to read + * @param encoding - Text encoding to use (default: 'utf8') + * @returns The decoded text content of the file + */ + const contentBytes = await this.readBytes(path); + return contentBytes.toString(encoding); + } + + async writeText( + path: string, + content: string, + encoding: BufferEncoding = 'utf8', + append: boolean = false + ): Promise { + /** + * Write text to a file with specified encoding. + * + * @param path - Path to the file to write + * @param content - Text content to write + * @param encoding - Text encoding to use (default: 'utf8') + * @param append - Whether to append to the file instead of overwriting + */ + const contentBytes = Buffer.from(content, encoding); + await this.writeBytes(path, contentBytes, append); + } + + async readBytes(path: string, offset: number = 0, length?: number): Promise { + // For large files, use chunked reading + if (length === undefined) { + // Get file size first to determine if we need chunking + const fileSize = await this.getFileSize(path); + // If file is larger than 5MB, read in chunks + if (fileSize > 5 * 1024 * 1024) { + const readLength = offset > 0 ? fileSize - offset : fileSize; + return await this.readBytesChunked(path, offset, readLength); + } + } + + const response = await this.sendCommand('read_bytes', { + path, + offset, + length, + }); if (!response.success) { throw new Error((response.error as string) || 'Failed to read file'); } return Buffer.from(response.content_b64 as string, 'base64'); } - async writeBytes(path: string, content: Buffer): Promise { + async writeBytes(path: string, content: Buffer, append: boolean = false): Promise { + // For large files, use chunked writing + if (content.length > 5 * 1024 * 1024) { + // 5MB threshold + await this.writeBytesChunked(path, content, append); + return; + } + const response = await this.sendCommand('write_bytes', { path, content_b64: content.toString('base64'), + append, }); if (!response.success) { throw new Error((response.error as string) || 'Failed to write file'); From 8e50547d264d70bb7bcaff0e370f6fa97fca48b4 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 1 Jul 2025 11:14:37 -0400 Subject: [PATCH 121/141] Added text encoding to README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7655e888..f752257a 100644 --- a/README.md +++ b/README.md @@ -285,8 +285,8 @@ await computer.interface.copy_to_clipboard() # Get clipboard content # File System Operations await computer.interface.file_exists(path) # Check if file exists await computer.interface.directory_exists(path) # Check if directory exists -await computer.interface.read_text(path) # Read file content -await computer.interface.write_text(path, content) # Write file content +await computer.interface.read_text(path, encoding="utf-8") # Read file content +await computer.interface.write_text(path, content, encoding="utf-8") # Write file content await computer.interface.read_bytes(path) # Read file content as bytes await computer.interface.write_bytes(path, content) # Write file content as bytes await computer.interface.delete_file(path) # Delete file From 40a06cac04eb5b9a326a53ca4e371dc4c9cc6509 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 1 Jul 2025 11:15:00 -0400 Subject: [PATCH 122/141] Updated tests; bumped mcp deps --- libs/python/mcp-server/pyproject.toml | 4 +- tests/files.py | 256 ++++++++++++++++++++++++++ 2 files changed, 258 insertions(+), 2 deletions(-) diff --git a/libs/python/mcp-server/pyproject.toml b/libs/python/mcp-server/pyproject.toml index 62fc1a2e..ed2ad435 100644 --- a/libs/python/mcp-server/pyproject.toml +++ b/libs/python/mcp-server/pyproject.toml @@ -13,8 +13,8 @@ authors = [ ] dependencies = [ "mcp>=1.6.0,<2.0.0", - "cua-agent[all]>=0.2.0,<0.3.0", - "cua-computer>=0.2.0,<0.3.0", + "cua-agent[all]>=0.3.0,<0.4.0", + "cua-computer>=0.3.0,<0.4.0", ] [project.scripts] diff --git a/tests/files.py b/tests/files.py index 236ef9e2..bcfbb4f5 100644 --- a/tests/files.py +++ b/tests/files.py @@ -136,6 +136,262 @@ async def test_create_dir(computer): assert exists is True, "Directory should exist" await computer.interface.delete_dir(tmp_dir) + +@pytest.mark.asyncio(loop_scope="session") +async def test_read_bytes_basic(computer): + """Test basic read_bytes functionality.""" + tmp_path = "test_read_bytes.bin" + test_data = b"Hello, World! This is binary data \x00\x01\x02\x03" + + # Write binary data using write_text (assuming it handles bytes) + await computer.interface.write_text(tmp_path, test_data.decode('latin-1')) + + # Read all bytes + read_data = await computer.interface.read_bytes(tmp_path) + assert read_data == test_data, "Binary data should match" + + await computer.interface.delete_file(tmp_path) + + +@pytest.mark.asyncio(loop_scope="session") +async def test_read_bytes_with_offset_and_length(computer): + """Test read_bytes with offset and length parameters.""" + tmp_path = "test_read_bytes_offset.bin" + test_data = b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + # Write test data + await computer.interface.write_text(tmp_path, test_data.decode('latin-1')) + + # Test reading with offset only + read_data = await computer.interface.read_bytes(tmp_path, offset=5) + expected = test_data[5:] + assert read_data == expected, f"Data from offset 5 should match. Got: {read_data}, Expected: {expected}" + + # Test reading with offset and length + read_data = await computer.interface.read_bytes(tmp_path, offset=10, length=5) + expected = test_data[10:15] + assert read_data == expected, f"Data from offset 10, length 5 should match. Got: {read_data}, Expected: {expected}" + + # Test reading from beginning with length + read_data = await computer.interface.read_bytes(tmp_path, offset=0, length=10) + expected = test_data[:10] + assert read_data == expected, f"Data from beginning, length 10 should match. Got: {read_data}, Expected: {expected}" + + await computer.interface.delete_file(tmp_path) + + +@pytest.mark.asyncio(loop_scope="session") +async def test_get_file_size(computer): + """Test get_file_size functionality.""" + tmp_path = "test_file_size.txt" + test_content = "A" * 1000 # 1000 bytes + + await computer.interface.write_text(tmp_path, test_content) + + file_size = await computer.interface.get_file_size(tmp_path) + assert file_size == 1000, f"File size should be 1000 bytes, got {file_size}" + + await computer.interface.delete_file(tmp_path) + + +@pytest.mark.asyncio(loop_scope="session") +async def test_read_large_file(computer): + """Test reading a file larger than 10MB to verify chunked reading.""" + tmp_path = "test_large_file.bin" + + # Create a file larger than 10MB (10 * 1024 * 1024 = 10,485,760 bytes) + total_size = 12 * 1024 * 1024 # 12MB + + print(f"Creating large file of {total_size} bytes ({total_size / (1024*1024):.1f}MB)...") + + # Create large file content (this will test the chunked writing functionality) + large_content = b"X" * total_size + + # Write the large file using write_bytes (will automatically use chunked writing) + await computer.interface.write_bytes(tmp_path, large_content) + + # Verify file size + file_size = await computer.interface.get_file_size(tmp_path) + assert file_size == total_size, f"Large file size should be {total_size} bytes, got {file_size}" + + print(f"Large file created successfully: {file_size} bytes") + + # Test reading the entire large file (should use chunked reading) + print("Reading large file...") + read_data = await computer.interface.read_bytes(tmp_path) + assert len(read_data) == total_size, f"Read data size should match file size. Got {len(read_data)}, expected {total_size}" + + # Verify content (should be all 'X' characters) + expected_data = b"X" * total_size + assert read_data == expected_data, "Large file content should be all 'X' characters" + + print("Large file read successfully!") + + # Test reading with offset and length on large file + offset = 5 * 1024 * 1024 # 5MB offset + length = 2 * 1024 * 1024 # 2MB length + read_data = await computer.interface.read_bytes(tmp_path, offset=offset, length=length) + assert len(read_data) == length, f"Partial read size should be {length}, got {len(read_data)}" + assert read_data == b"X" * length, "Partial read content should be all 'X' characters" + + print("Large file partial read successful!") + + # Clean up + await computer.interface.delete_file(tmp_path) + print("Large file test completed successfully!") + +@pytest.mark.asyncio(loop_scope="session") +async def test_read_write_text_with_encoding(computer): + """Test reading and writing text files with different encodings.""" + print("Testing text file operations with different encodings...") + + tmp_path = "test_encoding.txt" + + # Test UTF-8 encoding (default) + utf8_content = "Hello, 世界! 🌍 Ñoño café" + await computer.interface.write_text(tmp_path, utf8_content, encoding='utf-8') + read_utf8 = await computer.interface.read_text(tmp_path, encoding='utf-8') + assert read_utf8 == utf8_content, "UTF-8 content should match" + + # Test ASCII encoding + ascii_content = "Hello, World! Simple ASCII text." + await computer.interface.write_text(tmp_path, ascii_content, encoding='ascii') + read_ascii = await computer.interface.read_text(tmp_path, encoding='ascii') + assert read_ascii == ascii_content, "ASCII content should match" + + # Test Latin-1 encoding + latin1_content = "Café, naïve, résumé" + await computer.interface.write_text(tmp_path, latin1_content, encoding='latin-1') + read_latin1 = await computer.interface.read_text(tmp_path, encoding='latin-1') + assert read_latin1 == latin1_content, "Latin-1 content should match" + + # Clean up + await computer.interface.delete_file(tmp_path) + print("Text encoding test completed successfully!") + +@pytest.mark.asyncio(loop_scope="session") +async def test_write_text_append_mode(computer): + """Test appending text to files.""" + print("Testing text file append mode...") + + tmp_path = "test_append.txt" + + # Write initial content + initial_content = "First line\n" + await computer.interface.write_text(tmp_path, initial_content) + + # Append more content + append_content = "Second line\n" + await computer.interface.write_text(tmp_path, append_content, append=True) + + # Read and verify + final_content = await computer.interface.read_text(tmp_path) + expected_content = initial_content + append_content + assert final_content == expected_content, f"Expected '{expected_content}', got '{final_content}'" + + # Append one more line + third_content = "Third line\n" + await computer.interface.write_text(tmp_path, third_content, append=True) + + # Read and verify final result + final_content = await computer.interface.read_text(tmp_path) + expected_content = initial_content + append_content + third_content + assert final_content == expected_content, f"Expected '{expected_content}', got '{final_content}'" + + # Clean up + await computer.interface.delete_file(tmp_path) + print("Text append test completed successfully!") + +@pytest.mark.asyncio(loop_scope="session") +async def test_large_text_file(computer): + """Test reading and writing large text files (>5MB) to verify chunked operations.""" + print("Testing large text file operations...") + + tmp_path = "test_large_text.txt" + + # Create a large text content (approximately 6MB) + # Each line is about 100 characters, so 60,000 lines ≈ 6MB + line_template = "This is line {:06d} with some additional text to make it longer and reach about 100 chars.\n" + large_content = "" + num_lines = 60000 + + print(f"Generating large text content with {num_lines} lines...") + for i in range(num_lines): + large_content += line_template.format(i) + + content_size_mb = len(large_content.encode('utf-8')) / (1024 * 1024) + print(f"Generated text content size: {content_size_mb:.2f} MB") + + # Write the large text file + print("Writing large text file...") + await computer.interface.write_text(tmp_path, large_content) + + # Read the entire file back + print("Reading large text file...") + read_content = await computer.interface.read_text(tmp_path) + + # Verify content matches + assert read_content == large_content, "Large text file content should match exactly" + + # Test partial reading by reading as bytes and decoding specific portions + print("Testing partial text reading...") + + # Read first 1000 characters worth of bytes + first_1000_chars = large_content[:1000] + first_1000_bytes = first_1000_chars.encode('utf-8') + read_bytes = await computer.interface.read_bytes(tmp_path, offset=0, length=len(first_1000_bytes)) + decoded_partial = read_bytes.decode('utf-8') + assert decoded_partial == first_1000_chars, "Partial text reading should match" + + # Test appending to large file + print("Testing append to large text file...") + append_text = "\n--- APPENDED CONTENT ---\nThis content was appended to the large file.\n" + await computer.interface.write_text(tmp_path, append_text, append=True) + + # Read and verify appended content + final_content = await computer.interface.read_text(tmp_path) + expected_final = large_content + append_text + assert final_content == expected_final, "Appended large text file should match" + + # Clean up + await computer.interface.delete_file(tmp_path) + print("Large text file test completed successfully!") + +@pytest.mark.asyncio(loop_scope="session") +async def test_text_file_edge_cases(computer): + """Test edge cases for text file operations.""" + print("Testing text file edge cases...") + + tmp_path = "test_edge_cases.txt" + + # Test empty file + empty_content = "" + await computer.interface.write_text(tmp_path, empty_content) + read_empty = await computer.interface.read_text(tmp_path) + assert read_empty == empty_content, "Empty file should return empty string" + + # Test file with only whitespace + whitespace_content = " \n\t\r\n \n" + await computer.interface.write_text(tmp_path, whitespace_content) + read_whitespace = await computer.interface.read_text(tmp_path) + assert read_whitespace == whitespace_content, "Whitespace content should be preserved" + + # Test file with special characters and newlines + special_content = "Line 1\nLine 2\r\nLine 3\tTabbed\nSpecial: !@#$%^&*()\n" + await computer.interface.write_text(tmp_path, special_content) + read_special = await computer.interface.read_text(tmp_path) + assert read_special == special_content, "Special characters should be preserved" + + # Test very long single line (no newlines) + long_line = "A" * 10000 # 10KB single line + await computer.interface.write_text(tmp_path, long_line) + read_long_line = await computer.interface.read_text(tmp_path) + assert read_long_line == long_line, "Long single line should be preserved" + + # Clean up + await computer.interface.delete_file(tmp_path) + print("Text file edge cases test completed successfully!") + if __name__ == "__main__": # Run tests directly pytest.main([__file__, "-v"]) From 57b6efe83302d4a18a333cca8029e81109bceeb8 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 1 Jul 2025 12:24:59 -0400 Subject: [PATCH 123/141] implemented watchdog on server --- .../computer-server/computer_server/cli.py | 68 ++++ .../computer_server/watchdog.py | 337 ++++++++++++++++++ libs/python/computer-server/pyproject.toml | 3 +- 3 files changed, 407 insertions(+), 1 deletion(-) create mode 100644 libs/python/computer-server/computer_server/watchdog.py diff --git a/libs/python/computer-server/computer_server/cli.py b/libs/python/computer-server/computer_server/cli.py index 30f7e519..0fcbda00 100644 --- a/libs/python/computer-server/computer_server/cli.py +++ b/libs/python/computer-server/computer_server/cli.py @@ -3,11 +3,15 @@ Command-line interface for the Computer API server. """ import argparse +import asyncio import logging +import os import sys +import threading from typing import List, Optional from .server import Server +from .watchdog import Watchdog logger = logging.getLogger(__name__) @@ -37,6 +41,22 @@ def parse_args(args: Optional[List[str]] = None) -> argparse.Namespace: type=str, help="Path to SSL certificate file (enables HTTPS)", ) + parser.add_argument( + "--watchdog", + action="store_true", + help="Enable watchdog monitoring (automatically enabled if CONTAINER_NAME env var is set)", + ) + parser.add_argument( + "--watchdog-interval", + type=int, + default=30, + help="Watchdog ping interval in seconds (default: 30)", + ) + parser.add_argument( + "--no-restart", + action="store_true", + help="Disable automatic server restart in watchdog", + ) return parser.parse_args(args) @@ -51,6 +71,54 @@ def main() -> None: format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", ) + # Check if watchdog should be enabled + container_name = os.environ.get("CONTAINER_NAME") + enable_watchdog = args.watchdog or bool(container_name) + + if container_name: + logger.info(f"Container environment detected (CONTAINER_NAME={container_name}), enabling watchdog") + elif args.watchdog: + logger.info("Watchdog explicitly enabled via --watchdog flag") + + # Start watchdog if enabled + if enable_watchdog: + logger.info(f"Starting watchdog monitoring with {args.watchdog_interval}s interval") + + def run_watchdog_thread(): + """Run watchdog in a separate thread.""" + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + try: + # Create CLI args dict for watchdog + cli_args = { + 'host': args.host, + 'port': args.port, + 'log_level': args.log_level, + 'ssl_keyfile': args.ssl_keyfile, + 'ssl_certfile': args.ssl_certfile + } + + # Create watchdog with restart settings + watchdog = Watchdog( + cli_args=cli_args, + ping_interval=args.watchdog_interval + ) + watchdog.restart_enabled = not args.no_restart + + loop.run_until_complete(watchdog.start_monitoring()) + except Exception as e: + logger.error(f"Watchdog error: {e}") + finally: + loop.close() + + # Start watchdog in background thread + watchdog_thread = threading.Thread( + target=run_watchdog_thread, + daemon=True, + name="watchdog" + ) + watchdog_thread.start() + # Create and start the server logger.info(f"Starting CUA Computer API server on {args.host}:{args.port}...") diff --git a/libs/python/computer-server/computer_server/watchdog.py b/libs/python/computer-server/computer_server/watchdog.py new file mode 100644 index 00000000..bfa9fc65 --- /dev/null +++ b/libs/python/computer-server/computer_server/watchdog.py @@ -0,0 +1,337 @@ +""" +Watchdog module for monitoring the Computer API server health. +Unix/Linux only - provides process management and restart capabilities. +""" + +import asyncio +import fcntl +import json +import logging +import os +import platform +import subprocess +import sys +import time +import websockets +from typing import Optional + +logger = logging.getLogger(__name__) + + +def instance_already_running(label="watchdog"): + """ + Detect if an an instance with the label is already running, globally + at the operating system level. + + Using `os.open` ensures that the file pointer won't be closed + by Python's garbage collector after the function's scope is exited. + + The lock will be released when the program exits, or could be + released if the file pointer were closed. + """ + + lock_file_pointer = os.open(f"/tmp/instance_{label}.lock", os.O_WRONLY | os.O_CREAT) + + try: + fcntl.lockf(lock_file_pointer, fcntl.LOCK_EX | fcntl.LOCK_NB) + already_running = False + except IOError: + already_running = True + + return already_running + + +class Watchdog: + """Watchdog class to monitor server health via WebSocket connection. + Unix/Linux only - provides restart capabilities. + """ + + def __init__(self, cli_args: Optional[dict] = None, ping_interval: int = 30): + """ + Initialize the watchdog. + + Args: + cli_args: Dictionary of CLI arguments to replicate when restarting + ping_interval: Interval between ping checks in seconds + """ + # Check if running on Unix/Linux + if platform.system() not in ['Linux', 'Darwin']: + raise RuntimeError("Watchdog is only supported on Unix/Linux systems") + + # Store CLI arguments for restart + self.cli_args = cli_args or {} + self.host = self.cli_args.get('host', 'localhost') + self.port = self.cli_args.get('port', 8000) + self.ping_interval = ping_interval + self.container_name = os.environ.get("CONTAINER_NAME") + self.running = False + self.restart_enabled = True + + @property + def ws_uri(self) -> str: + """Get the WebSocket URI using the current IP address. + + Returns: + WebSocket URI for the Computer API Server + """ + ip_address = "localhost" if not self.container_name else f"{self.container_name}.containers.cloud.trycua.com" + protocol = "wss" if self.container_name else "ws" + port = "8443" if self.container_name else "8000" + return f"{protocol}://{ip_address}:{port}/ws" + + async def ping(self) -> bool: + """ + Test connection to the WebSocket endpoint. + + Returns: + True if connection successful, False otherwise + """ + try: + # Create a simple ping message + ping_message = { + "type": "ping", + "timestamp": time.time() + } + + # Try to connect to the WebSocket + async with websockets.connect( + self.ws_uri, + timeout=10, + max_size=1024 * 1024 * 10 # 10MB limit to match server + ) as websocket: + # Send ping message + await websocket.send(json.dumps(ping_message)) + + # Wait for any response or just close + try: + response = await asyncio.wait_for(websocket.recv(), timeout=5) + logger.debug(f"Ping response received: {response[:100]}...") + except asyncio.TimeoutError: + # No response is fine for a ping + logger.debug("Ping sent successfully (no response expected)") + + return True + + except Exception as e: + logger.warning(f"Ping failed: {e}") + return False + + def kill_processes_on_port(self, port: int) -> bool: + """ + Kill any processes using the specified port. + + Args: + port: Port number to check and kill processes on + + Returns: + True if processes were killed or none found, False on error + """ + try: + # Find processes using the port + result = subprocess.run( + ["lsof", "-ti", f":{port}"], + capture_output=True, + text=True, + timeout=10 + ) + + if result.returncode == 0 and result.stdout.strip(): + pids = result.stdout.strip().split('\n') + logger.info(f"Found {len(pids)} processes using port {port}: {pids}") + + # Kill each process + for pid in pids: + if pid.strip(): + try: + subprocess.run(["kill", "-9", pid.strip()], timeout=5) + logger.info(f"Killed process {pid}") + except subprocess.TimeoutExpired: + logger.warning(f"Timeout killing process {pid}") + except Exception as e: + logger.warning(f"Error killing process {pid}: {e}") + + return True + else: + logger.debug(f"No processes found using port {port}") + return True + + except subprocess.TimeoutExpired: + logger.error(f"Timeout finding processes on port {port}") + return False + except Exception as e: + logger.error(f"Error finding processes on port {port}: {e}") + return False + + def restart_server(self) -> bool: + """ + Attempt to restart the server by killing existing processes and starting new one. + + Returns: + True if restart was attempted, False on error + """ + if not self.restart_enabled: + logger.info("Server restart is disabled") + return False + + try: + logger.info("Attempting to restart server...") + + # Kill processes on the port + port_to_kill = 8443 if self.container_name else self.port + if not self.kill_processes_on_port(port_to_kill): + logger.error("Failed to kill processes on port, restart aborted") + return False + + # Wait a moment for processes to die + time.sleep(2) + + # Try to restart the server + # In container mode, we can't easily restart, so just log + if self.container_name: + logger.warning("Container mode detected - cannot restart server automatically") + logger.warning("Container orchestrator should handle restart") + return False + else: + # For local mode, try to restart the CLI + logger.info("Attempting to restart local server...") + + # Get the current Python executable and script + python_exe = sys.executable + + # Try to find the CLI module + try: + # Build command with all original CLI arguments + cmd = [python_exe, "-m", "computer_server.cli"] + + # Add all CLI arguments except watchdog-related ones + for key, value in self.cli_args.items(): + if key in ['watchdog', 'watchdog_interval', 'no_restart']: + continue # Skip watchdog args to avoid recursive watchdog + + # Convert underscores to hyphens for CLI args + arg_name = f"--{key.replace('_', '-')}" + + if isinstance(value, bool): + if value: # Only add flag if True + cmd.append(arg_name) + else: + cmd.extend([arg_name, str(value)]) + + logger.info(f"Starting server with command: {' '.join(cmd)}") + + # Start process in background + subprocess.Popen( + cmd, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + start_new_session=True + ) + + logger.info("Server restart initiated") + return True + + except Exception as e: + logger.error(f"Failed to restart server: {e}") + return False + + except Exception as e: + logger.error(f"Error during server restart: {e}") + return False + + async def start_monitoring(self) -> None: + """Start the watchdog monitoring loop.""" + self.running = True + logger.info(f"Starting watchdog monitoring for {self.ws_uri}") + logger.info(f"Ping interval: {self.ping_interval} seconds") + if self.container_name: + logger.info(f"Container mode detected: {self.container_name}") + + consecutive_failures = 0 + max_failures = 3 + + while self.running: + try: + success = await self.ping() + + if success: + if consecutive_failures > 0: + logger.info("Server connection restored") + consecutive_failures = 0 + logger.debug("Ping successful") + else: + consecutive_failures += 1 + logger.warning(f"Ping failed ({consecutive_failures}/{max_failures})") + + if consecutive_failures >= max_failures: + logger.error(f"Server appears to be down after {max_failures} consecutive failures") + + # Attempt to restart the server + if self.restart_enabled: + logger.info("Attempting automatic server restart...") + restart_success = self.restart_server() + + if restart_success: + logger.info("Server restart initiated, waiting before next ping...") + # Wait longer after restart attempt + await asyncio.sleep(self.ping_interval * 2) + consecutive_failures = 0 # Reset counter after restart attempt + else: + logger.error("Server restart failed") + else: + logger.warning("Automatic restart is disabled") + + # Wait for next ping interval + await asyncio.sleep(self.ping_interval) + + except asyncio.CancelledError: + logger.info("Watchdog monitoring cancelled") + break + except Exception as e: + logger.error(f"Unexpected error in watchdog loop: {e}") + await asyncio.sleep(self.ping_interval) + + def stop_monitoring(self) -> None: + """Stop the watchdog monitoring.""" + self.running = False + logger.info("Stopping watchdog monitoring") + + +async def run_watchdog(cli_args: Optional[dict] = None, ping_interval: int = 30) -> None: + """ + Run the watchdog monitoring. + + Args: + cli_args: Dictionary of CLI arguments to replicate when restarting + ping_interval: Interval between ping checks in seconds + """ + watchdog = Watchdog(cli_args=cli_args, ping_interval=ping_interval) + + try: + await watchdog.start_monitoring() + except KeyboardInterrupt: + logger.info("Watchdog stopped by user") + finally: + watchdog.stop_monitoring() + + +if __name__ == "__main__": + # For testing the watchdog standalone + import argparse + + parser = argparse.ArgumentParser(description="Run Computer API server watchdog") + parser.add_argument("--host", default="localhost", help="Server host to monitor") + parser.add_argument("--port", type=int, default=8000, help="Server port to monitor") + parser.add_argument("--ping-interval", type=int, default=30, help="Ping interval in seconds") + + args = parser.parse_args() + + logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + ) + + cli_args = { + 'host': args.host, + 'port': args.port + } + asyncio.run(run_watchdog(cli_args, args.ping_interval)) diff --git a/libs/python/computer-server/pyproject.toml b/libs/python/computer-server/pyproject.toml index 090da016..6e9e7240 100644 --- a/libs/python/computer-server/pyproject.toml +++ b/libs/python/computer-server/pyproject.toml @@ -20,7 +20,8 @@ dependencies = [ "pynput>=1.8.1", "pillow>=10.2.0", "aiohttp>=3.9.1", - "pyperclip>=1.9.0" + "pyperclip>=1.9.0", + "websockets>=12.0" ] [project.optional-dependencies] From cd5c0fbb2a9428fa746b948e5bff26fed17dce03 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 1 Jul 2025 12:39:26 -0400 Subject: [PATCH 124/141] Fix nonexistant kwarg --- libs/python/computer-server/computer_server/watchdog.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libs/python/computer-server/computer_server/watchdog.py b/libs/python/computer-server/computer_server/watchdog.py index bfa9fc65..a53c3042 100644 --- a/libs/python/computer-server/computer_server/watchdog.py +++ b/libs/python/computer-server/computer_server/watchdog.py @@ -89,14 +89,13 @@ class Watchdog: try: # Create a simple ping message ping_message = { - "type": "ping", - "timestamp": time.time() + "command": "get_screen_size", + "params": {} } # Try to connect to the WebSocket async with websockets.connect( self.ws_uri, - timeout=10, max_size=1024 * 1024 * 10 # 10MB limit to match server ) as websocket: # Send ping message From 62832f7beac7d5c9e2d7f537b7d165e568ecc1c0 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 1 Jul 2025 12:40:38 -0400 Subject: [PATCH 125/141] Added server recovery test --- tests/watchdog.py | 204 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 tests/watchdog.py diff --git a/tests/watchdog.py b/tests/watchdog.py new file mode 100644 index 00000000..c46b7517 --- /dev/null +++ b/tests/watchdog.py @@ -0,0 +1,204 @@ +""" +Watchdog Recovery Tests +Tests for the watchdog functionality to ensure server recovery after hanging commands. +Required environment variables: +- CUA_API_KEY: API key for C/ua cloud provider +- CUA_CONTAINER_NAME: Name of the container to use +""" + +import os +import asyncio +import pytest +from pathlib import Path +import sys +import traceback +import time + +# Load environment variables from .env file +project_root = Path(__file__).parent.parent +env_file = project_root / ".env" +print(f"Loading environment from: {env_file}") +from dotenv import load_dotenv + +load_dotenv(env_file) + +# Add paths to sys.path if needed +pythonpath = os.environ.get("PYTHONPATH", "") +for path in pythonpath.split(":"): + if path and path not in sys.path: + sys.path.insert(0, path) # Insert at beginning to prioritize + print(f"Added to sys.path: {path}") + +from computer import Computer, VMProviderType + +@pytest.fixture(scope="session") +async def computer(): + """Shared Computer instance for all test cases.""" + # Create a remote Linux computer with C/ua + computer = Computer( + os_type="linux", + api_key=os.getenv("CUA_API_KEY"), + name=str(os.getenv("CUA_CONTAINER_NAME")), + provider_type=VMProviderType.CLOUD, + ) + + try: + await computer.run() + yield computer + finally: + await computer.disconnect() + + +@pytest.mark.asyncio +async def test_simple_server_ping(computer): + """ + Simple test to verify server connectivity before running watchdog tests. + """ + print("Testing basic server connectivity...") + + try: + result = await computer.interface.run_command("echo 'Server ping test'") + print(f"Ping successful: {result}") + assert result is not None, "Server ping returned None" + print("✅ Server connectivity test passed") + except Exception as e: + print(f"❌ Server ping failed: {e}") + pytest.fail(f"Basic server connectivity test failed: {e}") + + +@pytest.mark.asyncio +async def test_watchdog_recovery_after_hanging_command(computer): + """ + Test that the watchdog can recover the server after a hanging command. + + This test runs two concurrent tasks: + 1. A long-running command that hangs the server (sleep 300 = 5 minutes) + 2. Periodic ping commands every 30 seconds to test server responsiveness + + The watchdog should detect the unresponsive server and restart it. + """ + print("Starting watchdog recovery test...") + + async def hanging_command(): + """Execute a command that takes 5 minutes to complete.""" + try: + print("Starting hanging command (sleep 300)...") + result = await computer.interface.run_command("sleep 300") + print(f"Hanging command completed: {result}") + return result + except Exception as e: + print(f"Hanging command failed (expected if watchdog restarts): {e}") + return None + + async def ping_server(): + """Ping the server every 30 seconds with echo commands.""" + ping_count = 0 + successful_pings = 0 + failed_pings = 0 + + try: + # Run pings for up to 4 minutes (8 pings at 30-second intervals) + for i in range(8): + try: + ping_count += 1 + print(f"Ping #{ping_count}: Sending echo command...") + + start_time = time.time() + result = await asyncio.wait_for( + computer.interface.run_command(f"echo 'Ping {ping_count} at {int(start_time)}'"), + timeout=10.0 # 10 second timeout for each ping + ) + end_time = time.time() + + print(f"Ping #{ping_count} successful in {end_time - start_time:.2f}s: {result}") + successful_pings += 1 + + except asyncio.TimeoutError: + print(f"Ping #{ping_count} timed out (server may be unresponsive)") + failed_pings += 1 + except Exception as e: + print(f"Ping #{ping_count} failed with exception: {e}") + failed_pings += 1 + + # Wait 30 seconds before next ping + if i < 7: # Don't wait after the last ping + print(f"Waiting 30 seconds before next ping...") + await asyncio.sleep(30) + + print(f"Ping summary: {successful_pings} successful, {failed_pings} failed") + return successful_pings, failed_pings + + except Exception as e: + print(f"Ping server function failed with critical error: {e}") + traceback.print_exc() + return successful_pings, failed_pings + + # Run both tasks concurrently + print("Starting concurrent tasks: hanging command and ping monitoring...") + + try: + # Use asyncio.gather to run both tasks concurrently + hanging_task = asyncio.create_task(hanging_command()) + ping_task = asyncio.create_task(ping_server()) + + # Wait for both tasks to complete or timeout after 5 minutes + done, pending = await asyncio.wait( + [hanging_task, ping_task], + timeout=300, # 5 minute timeout + return_when=asyncio.ALL_COMPLETED + ) + + # Cancel any pending tasks + for task in pending: + task.cancel() + try: + await task + except asyncio.CancelledError: + pass + + # Get results from completed tasks + ping_result = None + hanging_result = None + + if ping_task in done: + try: + ping_result = await ping_task + print(f"Ping task completed with result: {ping_result}") + except Exception as e: + print(f"Error getting ping task result: {e}") + traceback.print_exc() + + if hanging_task in done: + try: + hanging_result = await hanging_task + print(f"Hanging task completed with result: {hanging_result}") + except Exception as e: + print(f"Error getting hanging task result: {e}") + traceback.print_exc() + + # Analyze results + if ping_result: + successful_pings, failed_pings = ping_result + + # Test passes if we had some successful pings, indicating recovery + assert successful_pings > 0, f"No successful pings detected. Server may not have recovered." + + # If we had failures followed by successes, that indicates watchdog recovery + if failed_pings > 0 and successful_pings > 0: + print("✅ SUCCESS: Watchdog recovery detected - server became unresponsive then recovered") + elif successful_pings > 0 and failed_pings == 0: + print("✅ SUCCESS: Server remained responsive throughout test") + + print(f"Test completed: {successful_pings} successful pings, {failed_pings} failed pings") + else: + pytest.fail("Ping task did not complete - unable to assess server recovery") + + except Exception as e: + print(f"Test failed with exception: {e}") + traceback.print_exc() + pytest.fail(f"Watchdog recovery test failed: {e}") + + +if __name__ == "__main__": + # Run tests directly + pytest.main([__file__, "-v"]) From 46ac7e30177440578a242995164b58be01eb1867 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 1 Jul 2025 13:18:21 -0400 Subject: [PATCH 126/141] Fixed watchdog test --- .../python/computer-server/test_connection.py | 24 +++++++++++++--- tests/watchdog.py | 28 +++++++++++++------ 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/libs/python/computer-server/test_connection.py b/libs/python/computer-server/test_connection.py index dee73e8d..de4eb2df 100755 --- a/libs/python/computer-server/test_connection.py +++ b/libs/python/computer-server/test_connection.py @@ -13,10 +13,16 @@ import argparse import sys -async def test_connection(host="localhost", port=8000, keep_alive=False): +async def test_connection(host="localhost", port=8000, keep_alive=False, container_name=None): """Test connection to the Computer Server.""" - uri = f"ws://{host}:{port}/ws" - print(f"Connecting to {uri}...") + if container_name: + # Container mode: use WSS with container domain and port 8443 + uri = f"wss://{container_name}.containers.cloud.trycua.com:8443/ws" + print(f"Connecting to container {container_name} at {uri}...") + else: + # Local mode: use WS with specified host and port + uri = f"ws://{host}:{port}/ws" + print(f"Connecting to local server at {uri}...") try: async with websockets.connect(uri) as websocket: @@ -54,13 +60,23 @@ def parse_args(): parser = argparse.ArgumentParser(description="Test connection to Computer Server") parser.add_argument("--host", default="localhost", help="Host address (default: localhost)") parser.add_argument("--port", type=int, default=8000, help="Port number (default: 8000)") + parser.add_argument("--container-name", help="Container name for cloud connection (uses WSS and port 8443)") parser.add_argument("--keep-alive", action="store_true", help="Keep connection alive") return parser.parse_args() async def main(): args = parse_args() - success = await test_connection(args.host, args.port, args.keep_alive) + + # Convert hyphenated argument to underscore for function parameter + container_name = getattr(args, 'container_name', None) + + success = await test_connection( + host=args.host, + port=args.port, + keep_alive=args.keep_alive, + container_name=container_name + ) return 0 if success else 1 diff --git a/tests/watchdog.py b/tests/watchdog.py index c46b7517..24b268c2 100644 --- a/tests/watchdog.py +++ b/tests/watchdog.py @@ -49,7 +49,7 @@ async def computer(): await computer.disconnect() -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_simple_server_ping(computer): """ Simple test to verify server connectivity before running watchdog tests. @@ -66,7 +66,7 @@ async def test_simple_server_ping(computer): pytest.fail(f"Basic server connectivity test failed: {e}") -@pytest.mark.asyncio +@pytest.mark.asyncio(loop_scope="session") async def test_watchdog_recovery_after_hanging_command(computer): """ Test that the watchdog can recover the server after a hanging command. @@ -80,15 +80,16 @@ async def test_watchdog_recovery_after_hanging_command(computer): print("Starting watchdog recovery test...") async def hanging_command(): - """Execute a command that takes 5 minutes to complete.""" + """Execute a command that sleeps forever to hang the server.""" try: - print("Starting hanging command (sleep 300)...") - result = await computer.interface.run_command("sleep 300") - print(f"Hanging command completed: {result}") - return result + print("Starting hanging command (sleep infinity)...") + # Use a very long sleep that should never complete naturally + result = await computer.interface.run_command("sleep 999999") + print(f"Hanging command completed unexpectedly: {result}") + return True # Should never reach here if watchdog works except Exception as e: - print(f"Hanging command failed (expected if watchdog restarts): {e}") - return None + print(f"Hanging command interrupted (expected if watchdog restarts): {e}") + return None # Expected result when watchdog kills the process async def ping_server(): """Ping the server every 30 seconds with echo commands.""" @@ -183,13 +184,22 @@ async def test_watchdog_recovery_after_hanging_command(computer): # Test passes if we had some successful pings, indicating recovery assert successful_pings > 0, f"No successful pings detected. Server may not have recovered." + # Check if hanging command was killed (indicating watchdog restart) + if hanging_result is None: + print("✅ SUCCESS: Hanging command was killed - watchdog restart detected") + elif hanging_result is True: + print("⚠️ WARNING: Hanging command completed naturally - watchdog may not have restarted") + # If we had failures followed by successes, that indicates watchdog recovery if failed_pings > 0 and successful_pings > 0: print("✅ SUCCESS: Watchdog recovery detected - server became unresponsive then recovered") + # Additional check: hanging command should be None if watchdog worked + assert hanging_result is None, "Expected hanging command to be killed by watchdog restart" elif successful_pings > 0 and failed_pings == 0: print("✅ SUCCESS: Server remained responsive throughout test") print(f"Test completed: {successful_pings} successful pings, {failed_pings} failed pings") + print(f"Hanging command result: {hanging_result} (None = killed by watchdog, True = completed naturally)") else: pytest.fail("Ping task did not complete - unable to assess server recovery") From 178f69bdf5774207b8491164e1ddcb4629abfa2a Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 1 Jul 2025 15:34:47 -0400 Subject: [PATCH 127/141] Changed watchdog conditions --- libs/python/computer-server/computer_server/watchdog.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/libs/python/computer-server/computer_server/watchdog.py b/libs/python/computer-server/computer_server/watchdog.py index a53c3042..392d9bc0 100644 --- a/libs/python/computer-server/computer_server/watchdog.py +++ b/libs/python/computer-server/computer_server/watchdog.py @@ -105,12 +105,9 @@ class Watchdog: try: response = await asyncio.wait_for(websocket.recv(), timeout=5) logger.debug(f"Ping response received: {response[:100]}...") + return True except asyncio.TimeoutError: - # No response is fine for a ping - logger.debug("Ping sent successfully (no response expected)") - - return True - + return False except Exception as e: logger.warning(f"Ping failed: {e}") return False From 394ebaac3c2752d6766f4580b98bded35dee1949 Mon Sep 17 00:00:00 2001 From: Dillon DuPont Date: Tue, 1 Jul 2025 16:04:06 -0400 Subject: [PATCH 128/141] Completed python refactor migration --- .cursorignore | 2 +- .devcontainer/devcontainer.json | 31 ---------------- .gitignore | 6 +++- .vscode/launch.json | 22 ++++++------ .vscode/py.code-workspace | 36 +++++++++---------- Dockerfile | 2 +- README.md | 14 ++++---- docs/Developer-Guide.md | 19 ++++++---- libs/python/mcp-server/README.md | 6 ++-- .../mcp-server/scripts/start_mcp_server.sh | 2 +- notebooks/agent_nb.ipynb | 2 +- notebooks/computer_nb.ipynb | 2 +- notebooks/computer_server_nb.ipynb | 4 +-- pyproject.toml | 12 +++---- scripts/build.ps1 | 16 ++++----- scripts/build.sh | 16 ++++----- scripts/run-docker-dev.sh | 2 +- 17 files changed, 87 insertions(+), 107 deletions(-) diff --git a/.cursorignore b/.cursorignore index 12e8e403..57a5589d 100644 --- a/.cursorignore +++ b/.cursorignore @@ -154,7 +154,7 @@ weights/icon_detect/model.pt weights/icon_detect/model.pt.zip weights/icon_detect/model.pt.zip.part* -libs/omniparser/weights/icon_detect/model.pt +libs/python/omniparser/weights/icon_detect/model.pt # Example test data and output examples/test_data/ diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 4f9ceccc..aa2b82a6 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,7 +5,6 @@ }, "containerEnv": { "DISPLAY": "", - "PYTHONPATH": "/app/libs/core:/app/libs/computer:/app/libs/agent:/app/libs/som:/app/libs/pylume:/app/libs/computer-server:/app/libs/mcp-server", "PYLUME_HOST": "host.docker.internal" }, "forwardPorts": [7860], @@ -15,35 +14,5 @@ "onAutoForward": "silent" } }, - "customizations": { - "vscode": { - "extensions": [ - "ms-python.python", - "ms-python.black-formatter", - "charliermarsh.ruff", - "ms-python.vscode-pylance" - ], - "settings": { - "python.defaultInterpreterPath": "/usr/local/bin/python", - "python.terminal.activateEnvironment": false, - "[python]": { - "editor.formatOnSave": true, - "editor.defaultFormatter": "ms-python.black-formatter", - "editor.codeActionsOnSave": { - "source.organizeImports": "explicit" - } - }, - "python.linting.enabled": true, - "python.linting.ruffEnabled": true, - "python.formatting.provider": "black", - "files.watcherExclude": { - "**/.venv/**": true, - "**/node_modules/**": true, - "**/__pycache__/**": true, - "**/.pytest_cache/**": true - } - } - } - }, "postCreateCommand": "/bin/bash .devcontainer/post-install.sh" } diff --git a/.gitignore b/.gitignore index c5f78692..e623dda8 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,10 @@ __pycache__/ # C extensions *.so +node_modules/* +*/node_modules +**/node_modules + # Distribution / packaging .Python build/ @@ -155,7 +159,7 @@ weights/icon_detect/model.pt weights/icon_detect/model.pt.zip weights/icon_detect/model.pt.zip.part* -libs/omniparser/weights/icon_detect/model.pt +libs/python/omniparser/weights/icon_detect/model.pt # Example test data and output examples/test_data/ diff --git a/.vscode/launch.json b/.vscode/launch.json index eb7f1801..acfd84b2 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,7 @@ "python": "${workspaceFolder:cua-root}/.venv/bin/python", "cwd": "${workspaceFolder:cua-root}", "env": { - "PYTHONPATH": "${workspaceFolder:cua-root}/libs/core:${workspaceFolder:cua-root}/libs/computer:${workspaceFolder:cua-root}/libs/agent:${workspaceFolder:cua-root}/libs/som:${workspaceFolder:cua-root}/libs/pylume" + "PYTHONPATH": "${workspaceFolder:cua-root}/libs/python/core:${workspaceFolder:cua-root}/libs/python/computer:${workspaceFolder:cua-root}/libs/python/agent:${workspaceFolder:cua-root}/libs/python/som:${workspaceFolder:cua-root}/libs/python/pylume" } }, { @@ -23,7 +23,7 @@ "python": "${workspaceFolder:cua-root}/.venv/bin/python", "cwd": "${workspaceFolder:cua-root}", "env": { - "PYTHONPATH": "${workspaceFolder:cua-root}/libs/core:${workspaceFolder:cua-root}/libs/computer:${workspaceFolder:cua-root}/libs/agent:${workspaceFolder:cua-root}/libs/som:${workspaceFolder:cua-root}/libs/pylume" + "PYTHONPATH": "${workspaceFolder:cua-root}/libs/python/core:${workspaceFolder:cua-root}/libs/python/computer:${workspaceFolder:cua-root}/libs/python/agent:${workspaceFolder:cua-root}/libs/python/som:${workspaceFolder:cua-root}/libs/python/pylume" } }, { @@ -36,7 +36,7 @@ "python": "${workspaceFolder:cua-root}/.venv/bin/python", "cwd": "${workspaceFolder:cua-root}", "env": { - "PYTHONPATH": "${workspaceFolder:cua-root}/libs/core:${workspaceFolder:cua-root}/libs/computer:${workspaceFolder:cua-root}/libs/agent:${workspaceFolder:cua-root}/libs/som:${workspaceFolder:cua-root}/libs/pylume" + "PYTHONPATH": "${workspaceFolder:cua-root}/libs/python/core:${workspaceFolder:cua-root}/libs/python/computer:${workspaceFolder:cua-root}/libs/python/agent:${workspaceFolder:cua-root}/libs/python/som:${workspaceFolder:cua-root}/libs/python/pylume" } }, { @@ -49,7 +49,7 @@ "python": "${workspaceFolder:cua-root}/.venv/bin/python", "cwd": "${workspaceFolder:cua-root}", "env": { - "PYTHONPATH": "${workspaceFolder:cua-root}/libs/core:${workspaceFolder:cua-root}/libs/computer:${workspaceFolder:cua-root}/libs/agent:${workspaceFolder:cua-root}/libs/som:${workspaceFolder:cua-root}/libs/pylume" + "PYTHONPATH": "${workspaceFolder:cua-root}/libs/python/core:${workspaceFolder:cua-root}/libs/python/computer:${workspaceFolder:cua-root}/libs/python/agent:${workspaceFolder:cua-root}/libs/python/som:${workspaceFolder:cua-root}/libs/python/pylume" } }, { @@ -62,7 +62,7 @@ "python": "${workspaceFolder:cua-root}/.venv/bin/python", "cwd": "${workspaceFolder:cua-root}", "env": { - "PYTHONPATH": "${workspaceFolder:cua-root}/libs/core:${workspaceFolder:cua-root}/libs/computer:${workspaceFolder:cua-root}/libs/agent:${workspaceFolder:cua-root}/libs/som:${workspaceFolder:cua-root}/libs/pylume" + "PYTHONPATH": "${workspaceFolder:cua-root}/libs/python/core:${workspaceFolder:cua-root}/libs/python/computer:${workspaceFolder:cua-root}/libs/python/agent:${workspaceFolder:cua-root}/libs/python/som:${workspaceFolder:cua-root}/libs/python/pylume" } }, { @@ -84,7 +84,7 @@ "python": "${workspaceFolder:cua-root}/.venv/bin/python", "cwd": "${workspaceFolder:cua-root}", "env": { - "PYTHONPATH": "${workspaceFolder:cua-root}/libs/core:${workspaceFolder:cua-root}/libs/computer:${workspaceFolder:cua-root}/libs/agent:${workspaceFolder:cua-root}/libs/som:${workspaceFolder:cua-root}/libs/pylume" + "PYTHONPATH": "${workspaceFolder:cua-root}/libs/python/core:${workspaceFolder:cua-root}/libs/python/computer:${workspaceFolder:cua-root}/libs/python/agent:${workspaceFolder:cua-root}/libs/python/som:${workspaceFolder:cua-root}/libs/python/pylume" } }, { @@ -106,27 +106,27 @@ "python": "${workspaceFolder:cua-root}/.venv/bin/python", "cwd": "${workspaceFolder:cua-root}", "env": { - "PYTHONPATH": "${workspaceFolder:cua-root}/libs/core:${workspaceFolder:cua-root}/libs/computer:${workspaceFolder:cua-root}/libs/agent:${workspaceFolder:cua-root}/libs/som:${workspaceFolder:cua-root}/libs/pylume" + "PYTHONPATH": "${workspaceFolder:cua-root}/libs/python/core:${workspaceFolder:cua-root}/libs/python/computer:${workspaceFolder:cua-root}/libs/python/agent:${workspaceFolder:cua-root}/libs/python/som:${workspaceFolder:cua-root}/libs/python/pylume" } }, { "name": "Run Computer Server", "type": "debugpy", "request": "launch", - "program": "${workspaceFolder}/libs/computer-server/run_server.py", + "program": "${workspaceFolder}/libs/python/computer-server/run_server.py", "console": "integratedTerminal", "justMyCode": true, "python": "${workspaceFolder:cua-root}/.venv/bin/python", "cwd": "${workspaceFolder:cua-root}", "env": { - "PYTHONPATH": "${workspaceFolder:cua-root}/libs/core:${workspaceFolder:cua-root}/libs/computer:${workspaceFolder:cua-root}/libs/agent:${workspaceFolder:cua-root}/libs/som:${workspaceFolder:cua-root}/libs/pylume" + "PYTHONPATH": "${workspaceFolder:cua-root}/libs/python/core:${workspaceFolder:cua-root}/libs/python/computer:${workspaceFolder:cua-root}/libs/python/agent:${workspaceFolder:cua-root}/libs/python/som:${workspaceFolder:cua-root}/libs/python/pylume" } }, { "name": "Run Computer Server with Args", "type": "debugpy", "request": "launch", - "program": "${workspaceFolder}/libs/computer-server/run_server.py", + "program": "${workspaceFolder}/libs/python/computer-server/run_server.py", "args": [ "--host", "0.0.0.0", @@ -140,7 +140,7 @@ "python": "${workspaceFolder:cua-root}/.venv/bin/python", "cwd": "${workspaceFolder:cua-root}", "env": { - "PYTHONPATH": "${workspaceFolder:cua-root}/libs/core:${workspaceFolder:cua-root}/libs/computer-server" + "PYTHONPATH": "${workspaceFolder:cua-root}/libs/python/core:${workspaceFolder:cua-root}/libs/python/computer-server" } }, { diff --git a/.vscode/py.code-workspace b/.vscode/py.code-workspace index 11751d53..25324251 100644 --- a/.vscode/py.code-workspace +++ b/.vscode/py.code-workspace @@ -6,27 +6,27 @@ }, { "name": "computer", - "path": "../libs/computer" + "path": "../libs/python/computer" }, { "name": "agent", - "path": "../libs/agent" + "path": "../libs/python/agent" }, { "name": "som", - "path": "../libs/som" + "path": "../libs/python/som" }, { "name": "computer-server", - "path": "../libs/computer-server" + "path": "../libs/python/computer-server" }, { "name": "pylume", - "path": "../libs/pylume" + "path": "../libs/python/pylume" }, { "name": "core", - "path": "../libs/core" + "path": "../libs/python/core" } ], "settings": { @@ -47,11 +47,11 @@ "libs" ], "python.analysis.extraPaths": [ - "${workspaceFolder:cua-root}/libs/core", - "${workspaceFolder:cua-root}/libs/computer", - "${workspaceFolder:cua-root}/libs/agent", - "${workspaceFolder:cua-root}/libs/som", - "${workspaceFolder:cua-root}/libs/pylume", + "${workspaceFolder:cua-root}/libs/python/core", + "${workspaceFolder:cua-root}/libs/python/computer", + "${workspaceFolder:cua-root}/libs/python/agent", + "${workspaceFolder:cua-root}/libs/python/som", + "${workspaceFolder:cua-root}/libs/python/pylume", "${workspaceFolder:cua-root}/.vscode/typings" ], "python.envFile": "${workspaceFolder:cua-root}/.env", @@ -99,11 +99,11 @@ } ], "python.autoComplete.extraPaths": [ - "${workspaceFolder:cua-root}/libs/core", - "${workspaceFolder:cua-root}/libs/computer", - "${workspaceFolder:cua-root}/libs/agent", - "${workspaceFolder:cua-root}/libs/som", - "${workspaceFolder:cua-root}/libs/pylume" + "${workspaceFolder:cua-root}/libs/python/core", + "${workspaceFolder:cua-root}/libs/python/computer", + "${workspaceFolder:cua-root}/libs/python/agent", + "${workspaceFolder:cua-root}/libs/python/som", + "${workspaceFolder:cua-root}/libs/python/pylume" ], "python.languageServer": "None", "[python]": { @@ -118,8 +118,8 @@ "examples/agent_examples.py": "python" }, "python.interpreterPaths": { - "examples/computer_examples.py": "${workspaceFolder}/libs/computer/.venv/bin/python", - "examples/agent_examples.py": "${workspaceFolder}/libs/agent/.venv/bin/python" + "examples/computer_examples.py": "${workspaceFolder}/libs/python/computer/.venv/bin/python", + "examples/agent_examples.py": "${workspaceFolder}/libs/python/agent/.venv/bin/python" } }, "tasks": { diff --git a/Dockerfile b/Dockerfile index 7762b43d..9b9f3c47 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,7 @@ ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ PIP_NO_CACHE_DIR=1 \ PIP_DISABLE_PIP_VERSION_CHECK=1 \ - PYTHONPATH="/app/libs/core:/app/libs/computer:/app/libs/agent:/app/libs/som:/app/libs/pylume:/app/libs/computer-server" + PYTHONPATH="/app/libs/python/core:/app/libs/python/computer:/app/libs/python/agent:/app/libs/python/som:/app/libs/python/pylume:/app/libs/python/computer-server:/app/libs/python/mcp-server" # Install system dependencies for ARM architecture RUN apt-get update && apt-get install -y --no-install-recommends \ diff --git a/README.md b/README.md index f752257a..4f2da313 100644 --- a/README.md +++ b/README.md @@ -97,11 +97,11 @@ Or check out the [Usage Guide](#-usage-guide) to learn how to use our Python SDK --- -## Supported [Agent Loops](https://github.com/trycua/cua/blob/main/libs/agent/README.md#agent-loops) -- [UITARS-1.5](https://github.com/trycua/cua/blob/main/libs/agent/README.md#agent-loops) - Run locally on Apple Silicon with MLX, or use cloud providers -- [OpenAI CUA](https://github.com/trycua/cua/blob/main/libs/agent/README.md#agent-loops) - Use OpenAI's Computer-Use Preview model -- [Anthropic CUA](https://github.com/trycua/cua/blob/main/libs/agent/README.md#agent-loops) - Use Anthropic's Computer-Use capabilities -- [OmniParser-v2.0](https://github.com/trycua/cua/blob/main/libs/agent/README.md#agent-loops) - Control UI with [Set-of-Marks prompting](https://som-gpt4v.github.io/) using any vision model +## Supported [Agent Loops](https://github.com/trycua/cua/blob/main/libs/python/agent/README.md#agent-loops) +- [UITARS-1.5](https://github.com/trycua/cua/blob/main/libs/python/agent/README.md#agent-loops) - Run locally on Apple Silicon with MLX, or use cloud providers +- [OpenAI CUA](https://github.com/trycua/cua/blob/main/libs/python/agent/README.md#agent-loops) - Use OpenAI's Computer-Use Preview model +- [Anthropic CUA](https://github.com/trycua/cua/blob/main/libs/python/agent/README.md#agent-loops) - Use Anthropic's Computer-Use capabilities +- [OmniParser-v2.0](https://github.com/trycua/cua/blob/main/libs/python/agent/README.md#agent-loops) - Control UI with [Set-of-Marks prompting](https://som-gpt4v.github.io/) using any vision model ## 🖥️ Compatibility @@ -224,8 +224,8 @@ docker run -it --rm \ ## Resources -- [How to use the MCP Server with Claude Desktop or other MCP clients](./libs/mcp-server/README.md) - One of the easiest ways to get started with C/ua -- [How to use OpenAI Computer-Use, Anthropic, OmniParser, or UI-TARS for your Computer-Use Agent](./libs/agent/README.md) +- [How to use the MCP Server with Claude Desktop or other MCP clients](./libs/python/mcp-server/README.md) - One of the easiest ways to get started with C/ua +- [How to use OpenAI Computer-Use, Anthropic, OmniParser, or UI-TARS for your Computer-Use Agent](./libs/python/agent/README.md) - [How to use Lume CLI for managing desktops](./libs/lume/README.md) - [Training Computer-Use Models: Collecting Human Trajectories with C/ua (Part 1)](https://www.trycua.com/blog/training-computer-use-models-trajectories-1) - [Build Your Own Operator on macOS (Part 1)](https://www.trycua.com/blog/build-your-own-operator-on-macos-1) diff --git a/docs/Developer-Guide.md b/docs/Developer-Guide.md index 31cf1c12..49aa82ee 100644 --- a/docs/Developer-Guide.md +++ b/docs/Developer-Guide.md @@ -4,13 +4,20 @@ The project is organized as a monorepo with these main packages: -- `libs/core/` - Base package with telemetry support -- `libs/computer/` - Computer-use interface (CUI) library -- `libs/agent/` - AI agent library with multi-provider support -- `libs/som/` - Set-of-Mark parser -- `libs/computer-server/` - Server component for VM +### Python +- `libs/python/core/` - Base package with telemetry support +- `libs/python/computer/` - Computer-use interface (CUI) library +- `libs/python/agent/` - AI agent library with multi-provider support +- `libs/python/som/` - Set-of-Mark parser +- `libs/python/computer-server/` - Server component for VM +- `libs/python/pylume/` - Python bindings for Lume + +### TypeScript +- `libs/typescript/computer/` - Computer-use interface (CUI) library +- `libs/typescript/agent/` - AI agent library with multi-provider support + +### Other - `libs/lume/` - Lume CLI -- `libs/pylume/` - Python bindings for Lume Each package has its own virtual environment and dependencies, managed through PDM. diff --git a/libs/python/mcp-server/README.md b/libs/python/mcp-server/README.md index 736ab364..3f3c8bbb 100644 --- a/libs/python/mcp-server/README.md +++ b/libs/python/mcp-server/README.md @@ -47,7 +47,7 @@ This will install: If you want to simplify installation, you can use this one-liner to download and run the installation script: ```bash -curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/libs/mcp-server/scripts/install_mcp_server.sh | bash +curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/libs/python/mcp-server/scripts/install_mcp_server.sh | bash ``` This script will: @@ -84,7 +84,7 @@ If you want to develop with the cua-mcp-server directly without installation, yo "mcpServers": { "cua-agent": { "command": "/bin/bash", - "args": ["~/cua/libs/mcp-server/scripts/start_mcp_server.sh"], + "args": ["~/cua/libs/python/mcp-server/scripts/start_mcp_server.sh"], "env": { "CUA_AGENT_LOOP": "UITARS", "CUA_MODEL_PROVIDER": "OAICOMPAT", @@ -106,7 +106,7 @@ Just add this to your MCP client's configuration and it will use your local deve ### Troubleshooting -If you get a `/bin/bash: ~/cua/libs/mcp-server/scripts/start_mcp_server.sh: No such file or directory` error, try changing the path to the script to be absolute instead of relative. +If you get a `/bin/bash: ~/cua/libs/python/mcp-server/scripts/start_mcp_server.sh: No such file or directory` error, try changing the path to the script to be absolute instead of relative. To see the logs: ``` diff --git a/libs/python/mcp-server/scripts/start_mcp_server.sh b/libs/python/mcp-server/scripts/start_mcp_server.sh index 17fd9dab..13257351 100755 --- a/libs/python/mcp-server/scripts/start_mcp_server.sh +++ b/libs/python/mcp-server/scripts/start_mcp_server.sh @@ -8,7 +8,7 @@ CUA_REPO_DIR="$( cd "$SCRIPT_DIR/../../.." &> /dev/null && pwd )" PYTHON_PATH="${CUA_REPO_DIR}/.venv/bin/python" # Set Python path to include all necessary libraries -export PYTHONPATH="${CUA_REPO_DIR}/libs/mcp-server:${CUA_REPO_DIR}/libs/agent:${CUA_REPO_DIR}/libs/computer:${CUA_REPO_DIR}/libs/core:${CUA_REPO_DIR}/libs/pylume" +export PYTHONPATH="${CUA_REPO_DIR}/libs/python/mcp-server:${CUA_REPO_DIR}/libs/python/agent:${CUA_REPO_DIR}/libs/python/computer:${CUA_REPO_DIR}/libs/python/core:${CUA_REPO_DIR}/libs/python/pylume" # Run the MCP server directly as a module $PYTHON_PATH -m mcp_server.server \ No newline at end of file diff --git a/notebooks/agent_nb.ipynb b/notebooks/agent_nb.ipynb index 84d67574..e4f6df84 100644 --- a/notebooks/agent_nb.ipynb +++ b/notebooks/agent_nb.ipynb @@ -49,7 +49,7 @@ "# If locally installed, use this instead:\n", "import os\n", "\n", - "os.chdir('../libs/agent')\n", + "os.chdir('../libs/python/agent')\n", "!poetry install\n", "!poetry build\n", "\n", diff --git a/notebooks/computer_nb.ipynb b/notebooks/computer_nb.ipynb index c0bd8460..c95d37f2 100644 --- a/notebooks/computer_nb.ipynb +++ b/notebooks/computer_nb.ipynb @@ -41,7 +41,7 @@ "source": [ "import os\n", "\n", - "os.chdir('../libs/computer')\n", + "os.chdir('../libs/python/computer')\n", "!poetry install\n", "!poetry build\n", "\n", diff --git a/notebooks/computer_server_nb.ipynb b/notebooks/computer_server_nb.ipynb index 536e4e67..fee6e048 100644 --- a/notebooks/computer_server_nb.ipynb +++ b/notebooks/computer_server_nb.ipynb @@ -34,7 +34,7 @@ "# If locally installed, use this instead:\n", "import os\n", "\n", - "os.chdir('../libs/computer-server')\n", + "os.chdir('../libs/python/computer-server')\n", "!pdm install\n", "!pdm build\n", "\n", @@ -109,7 +109,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.7" + "version": "3.12.2" } }, "nbformat": 4, diff --git a/pyproject.toml b/pyproject.toml index 36fa43c1..2330fbe9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,12 +24,12 @@ distribution = false [tool.pdm.dev-dependencies] dev = [ - "-e agent @ file:///${PROJECT_ROOT}/libs/agent", - "-e computer @ file:///${PROJECT_ROOT}/libs/computer", - "-e computer-server @ file:///${PROJECT_ROOT}/libs/computer-server", - "-e cua-som @ file:///${PROJECT_ROOT}/libs/som", - "-e mcp-server @ file:///${PROJECT_ROOT}/libs/mcp-server", - "-e pylume @ file:///${PROJECT_ROOT}/libs/pylume", + "-e agent @ file:///${PROJECT_ROOT}/libs/python/agent", + "-e computer @ file:///${PROJECT_ROOT}/libs/python/computer", + "-e computer-server @ file:///${PROJECT_ROOT}/libs/python/computer-server", + "-e cua-som @ file:///${PROJECT_ROOT}/libs/python/som", + "-e mcp-server @ file:///${PROJECT_ROOT}/libs/python/mcp-server", + "-e pylume @ file:///${PROJECT_ROOT}/libs/python/pylume", "black>=23.0.0", "ipykernel>=6.29.5", "jedi>=0.19.2", diff --git a/scripts/build.ps1 b/scripts/build.ps1 index 8c2c99c2..13f613d7 100644 --- a/scripts/build.ps1 +++ b/scripts/build.ps1 @@ -123,25 +123,25 @@ function Install-Package { Print-Step "Installing packages in development mode..." # Install core first (base package with telemetry support) -if (-not (Install-Package "libs/core" "core")) { exit 1 } +if (-not (Install-Package "libs/python/core" "core")) { exit 1 } # Install pylume (base dependency) -if (-not (Install-Package "libs/pylume" "pylume")) { exit 1 } +if (-not (Install-Package "libs/python/pylume" "pylume")) { exit 1 } # Install computer with all its dependencies and extras -if (-not (Install-Package "libs/computer" "computer" "all")) { exit 1 } +if (-not (Install-Package "libs/python/computer" "computer" "all")) { exit 1 } # Install omniparser -if (-not (Install-Package "libs/som" "som")) { exit 1 } +if (-not (Install-Package "libs/python/som" "som")) { exit 1 } # Install agent with all its dependencies and extras -if (-not (Install-Package "libs/agent" "agent" "all")) { exit 1 } +if (-not (Install-Package "libs/python/agent" "agent" "all")) { exit 1 } # Install computer-server -if (-not (Install-Package "libs/computer-server" "computer-server")) { exit 1 } +if (-not (Install-Package "libs/python/computer-server" "computer-server")) { exit 1 } # Install mcp-server -if (-not (Install-Package "libs/mcp-server" "mcp-server")) { exit 1 } +if (-not (Install-Package "libs/python/mcp-server" "mcp-server")) { exit 1 } # Install development tools from root project Print-Step "Installing development dependencies..." @@ -149,7 +149,7 @@ pip install -e ".[dev,test,docs]" # Create a .env file for VS Code to use the virtual environment Print-Step "Creating .env file for VS Code..." -$pythonPath = "$PROJECT_ROOT/libs/core;$PROJECT_ROOT/libs/computer;$PROJECT_ROOT/libs/agent;$PROJECT_ROOT/libs/som;$PROJECT_ROOT/libs/pylume;$PROJECT_ROOT/libs/computer-server;$PROJECT_ROOT/libs/mcp-server" +$pythonPath = "$PROJECT_ROOT/libs/python/core;$PROJECT_ROOT/libs/python/computer;$PROJECT_ROOT/libs/python/agent;$PROJECT_ROOT/libs/python/som;$PROJECT_ROOT/libs/python/pylume;$PROJECT_ROOT/libs/python/computer-server;$PROJECT_ROOT/libs/python/mcp-server" "PYTHONPATH=$pythonPath" | Out-File -FilePath ".env" -Encoding UTF8 Print-Success "All packages installed successfully!" diff --git a/scripts/build.sh b/scripts/build.sh index 19021423..f2850aae 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -87,25 +87,25 @@ install_package() { print_step "Installing packages in development mode..." # Install core first (base package with telemetry support) -install_package "libs/core" "core" +install_package "libs/python/core" "core" # Install pylume (base dependency) -install_package "libs/pylume" "pylume" +install_package "libs/python/pylume" "pylume" # Install computer with all its dependencies and extras -install_package "libs/computer" "computer" "all" +install_package "libs/python/computer" "computer" "all" # Install omniparser -install_package "libs/som" "som" +install_package "libs/python/som" "som" # Install agent with all its dependencies and extras -install_package "libs/agent" "agent" "all" +install_package "libs/python/agent" "agent" "all" # Install computer-server -install_package "libs/computer-server" "computer-server" +install_package "libs/python/computer-server" "computer-server" # Install mcp-server -install_package "libs/mcp-server" "mcp-server" +install_package "libs/python/mcp-server" "mcp-server" # Install development tools from root project print_step "Installing development dependencies..." @@ -113,7 +113,7 @@ pip install -e ".[dev,test,docs]" # Create a .env file for VS Code to use the virtual environment print_step "Creating .env file for VS Code..." -echo "PYTHONPATH=${PROJECT_ROOT}/libs/core:${PROJECT_ROOT}/libs/computer:${PROJECT_ROOT}/libs/agent:${PROJECT_ROOT}/libs/som:${PROJECT_ROOT}/libs/pylume:${PROJECT_ROOT}/libs/computer-server:${PROJECT_ROOT}/libs/mcp-server" > .env +echo "PYTHONPATH=${PROJECT_ROOT}/libs/python/core:${PROJECT_ROOT}/libs/python/computer:${PROJECT_ROOT}/libs/python/agent:${PROJECT_ROOT}/libs/python/som:${PROJECT_ROOT}/libs/python/pylume:${PROJECT_ROOT}/libs/python/computer-server:${PROJECT_ROOT}/libs/python/mcp-server" > .env print_success "All packages installed successfully!" print_step "Your virtual environment is ready. To activate it:" diff --git a/scripts/run-docker-dev.sh b/scripts/run-docker-dev.sh index d1301d09..e4aab8ea 100755 --- a/scripts/run-docker-dev.sh +++ b/scripts/run-docker-dev.sh @@ -43,7 +43,7 @@ else fi # Environment variables -PYTHONPATH="/app/libs/core:/app/libs/computer:/app/libs/agent:/app/libs/som:/app/libs/pylume:/app/libs/computer-server" +PYTHONPATH="/app/libs/python/core:/app/libs/python/computer:/app/libs/python/agent:/app/libs/python/som:/app/libs/python/pylume:/app/libs/python/computer-server:/app/libs/python/mcp-server" # Check if Docker is installed if ! command -v docker &> /dev/null; then From 990e538b970fda07a423feb82458cb49501c1d3f Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 2 Jul 2025 10:39:10 -0700 Subject: [PATCH 129/141] Fix last couple of links to libs --- README.md | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 4f2da313..d418a67a 100644 --- a/README.md +++ b/README.md @@ -51,21 +51,23 @@ **Need to automate desktop tasks? Launch the Computer-Use Agent UI with a single command.** - - ### Option 1: Fully-managed install with Docker (recommended) + *Docker-based guided install for quick use* **macOS/Linux/Windows (via WSL):** + ```bash # Requires Docker /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/scripts/playground-docker.sh)" ``` + This script will guide you through setup using Docker containers and launch the Computer-Use Agent UI. --- ### Option 2: [Dev Container](./.devcontainer/README.md) + *Best for contributors and development* This repository includes a [Dev Container](./.devcontainer/README.md) configuration that simplifies setup to a few steps: @@ -84,6 +86,7 @@ This repository includes a [Dev Container](./.devcontainer/README.md) configurat --- ### Option 3: PyPI + *Direct Python package installation* ```bash @@ -98,6 +101,7 @@ Or check out the [Usage Guide](#-usage-guide) to learn how to use our Python SDK --- ## Supported [Agent Loops](https://github.com/trycua/cua/blob/main/libs/python/agent/README.md#agent-loops) + - [UITARS-1.5](https://github.com/trycua/cua/blob/main/libs/python/agent/README.md#agent-loops) - Run locally on Apple Silicon with MLX, or use cloud providers - [OpenAI CUA](https://github.com/trycua/cua/blob/main/libs/python/agent/README.md#agent-loops) - Use OpenAI's Computer-Use Preview model - [Anthropic CUA](https://github.com/trycua/cua/blob/main/libs/python/agent/README.md#agent-loops) - Use Anthropic's Computer-Use capabilities @@ -236,12 +240,14 @@ docker run -it --rm \ |--------|-------------|---------------| | [**Lume**](./libs/lume/README.md) | VM management for macOS/Linux using Apple's Virtualization.Framework | `curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/libs/lume/scripts/install.sh \| bash` | | [**Lumier**](./libs/lumier/README.md) | Docker interface for macOS and Linux VMs | `docker pull trycua/lumier:latest` | -| [**Computer**](./libs/computer/README.md) | Interface for controlling virtual machines | `pip install "cua-computer[all]"` | -| [**Agent**](./libs/agent/README.md) | AI agent framework for automating tasks | `pip install "cua-agent[all]"` | -| [**MCP Server**](./libs/mcp-server/README.md) | MCP server for using CUA with Claude Desktop | `pip install cua-mcp-server` | -| [**SOM**](./libs/som/README.md) | Self-of-Mark library for Agent | `pip install cua-som` | -| [**Computer Server**](./libs/computer-server/README.md) | Server component for Computer | `pip install cua-computer-server` | -| [**Core**](./libs/core/README.md) | Core utilities | `pip install cua-core` | +| [**Computer (Python)**](./libs/python/computer/README.md) | Python Interface for controlling virtual machines | `pip install "cua-computer[all]"` | +| [**Computer (Typescript)**](./libs/typescript/computer/README.md) | Typescript Interface for controlling virtual machines | `npm install @trycua/computer` | +| [**Agent**](./libs/python/agent/README.md) | AI agent framework for automating tasks | `pip install "cua-agent[all]"` | +| [**MCP Server**](./libs/python/mcp-server/README.md) | MCP server for using CUA with Claude Desktop | `pip install cua-mcp-server` | +| [**SOM**](./libs/python/som/README.md) | Self-of-Mark library for Agent | `pip install cua-som` | +| [**Computer Server**](./libs/python/computer-server/README.md) | Server component for Computer | `pip install cua-computer-server` | +| [**Core (Python)**](./libs/python/core/README.md) | Python Core utilities | `pip install cua-core` | +| [**Core (Typescript)**](./libs/typescript/core/README.md) | Typescript Core utilities | `npm install @trycua/core` | ## Computer Interface Reference @@ -350,7 +356,6 @@ ComputerAgent( ) ``` - ## Community Join our [Discord community](https://discord.com/invite/mVnXXpdE85) to discuss ideas, get assistance, or share your demos! From 0c777e0582e5e644b4d2ddcf6275460e70593344 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 2 Jul 2025 11:18:32 -0700 Subject: [PATCH 130/141] Fix action issues with typescript libs, warnings on core lib --- .github/workflows/npm-publish-computer.yml | 7 ++++--- .github/workflows/npm-publish-core.yml | 7 ++++--- libs/typescript/core/tsdown.config.ts | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/npm-publish-computer.yml b/.github/workflows/npm-publish-computer.yml index 328378d7..e2949f4c 100644 --- a/.github/workflows/npm-publish-computer.yml +++ b/.github/workflows/npm-publish-computer.yml @@ -22,14 +22,15 @@ jobs: - name: Install dependencies working-directory: ./libs/typescript/computer - run: npm ci + run: pnpm install --frozen-lockfile - name: Build package working-directory: ./libs/typescript/computer - run: npm run build --if-present + run: pnpm run build --if-present - name: Publish to npm working-directory: ./libs/typescript/computer - run: npm publish --provenance --access public + run: pnpm publish --access public --no-git-checks env: + NPM_CONFIG_PROVENANCE: true NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/npm-publish-core.yml b/.github/workflows/npm-publish-core.yml index dc06628d..3f02f8f2 100644 --- a/.github/workflows/npm-publish-core.yml +++ b/.github/workflows/npm-publish-core.yml @@ -22,14 +22,15 @@ jobs: - name: Install dependencies working-directory: ./libs/typescript/core - run: npm ci + run: pnpm install --frozen-lockfile - name: Build package working-directory: ./libs/typescript/core - run: npm run build --if-present + run: pnpm run build --if-present - name: Publish to npm working-directory: ./libs/typescript/core - run: npm publish --provenance --access public + run: pnpm publish --access public --no-git-checks env: + NPM_CONFIG_PROVENANCE: true NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/libs/typescript/core/tsdown.config.ts b/libs/typescript/core/tsdown.config.ts index 21d62816..36743757 100644 --- a/libs/typescript/core/tsdown.config.ts +++ b/libs/typescript/core/tsdown.config.ts @@ -3,7 +3,7 @@ import { defineConfig } from 'tsdown'; export default defineConfig([ { entry: ['./src/index.ts'], - platform: 'neutral', + platform: 'node', dts: true, }, ]); From b84f037134ea130a1061507a6f834000018af252 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 2 Jul 2025 12:20:13 -0700 Subject: [PATCH 131/141] Update npm packages to publish if version changes --- .github/workflows/npm-publish-computer.yml | 14 ++++++++++++-- .github/workflows/npm-publish-core.yml | 14 ++++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/.github/workflows/npm-publish-computer.yml b/.github/workflows/npm-publish-computer.yml index e2949f4c..f8240d64 100644 --- a/.github/workflows/npm-publish-computer.yml +++ b/.github/workflows/npm-publish-computer.yml @@ -2,8 +2,7 @@ name: Publish @trycua/computer to npm on: push: - tags: - - "computer-v*" + branches: main jobs: publish: @@ -20,15 +19,26 @@ jobs: node-version: "24.x" registry-url: "https://registry.npmjs.org" + - name: Check if version changed + id: check-version + uses: EndBug/version-check@v2 + with: + file-name: libs/typescript/computer/package.json + static-checking: localIsNew + diff-search: true + - name: Install dependencies + if: steps.check-version.outputs.changed == 'true' working-directory: ./libs/typescript/computer run: pnpm install --frozen-lockfile - name: Build package + if: steps.check-version.outputs.changed == 'true' working-directory: ./libs/typescript/computer run: pnpm run build --if-present - name: Publish to npm + if: steps.check-version.outputs.changed == 'true' working-directory: ./libs/typescript/computer run: pnpm publish --access public --no-git-checks env: diff --git a/.github/workflows/npm-publish-core.yml b/.github/workflows/npm-publish-core.yml index 3f02f8f2..7d75e6c6 100644 --- a/.github/workflows/npm-publish-core.yml +++ b/.github/workflows/npm-publish-core.yml @@ -2,8 +2,7 @@ name: Publish @trycua/core to npm on: push: - tags: - - "core-v*" + branches: main jobs: publish: @@ -20,15 +19,26 @@ jobs: node-version: "24.x" registry-url: "https://registry.npmjs.org" + - name: Check if version changed + id: check-version + uses: EndBug/version-check@v2 + with: + file-name: libs/typescript/core/package.json + static-checking: localIsNew + diff-search: true + - name: Install dependencies + if: steps.check-version.outputs.changed == 'true' working-directory: ./libs/typescript/core run: pnpm install --frozen-lockfile - name: Build package + if: steps.check-version.outputs.changed == 'true' working-directory: ./libs/typescript/core run: pnpm run build --if-present - name: Publish to npm + if: steps.check-version.outputs.changed == 'true' working-directory: ./libs/typescript/core run: pnpm publish --access public --no-git-checks env: From a4a662253dcfc1967e9ea26888459ee0ab3f8987 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 2 Jul 2025 12:45:07 -0700 Subject: [PATCH 132/141] Remove static-checking from workflows --- .github/workflows/npm-publish-computer.yml | 1 - .github/workflows/npm-publish-core.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/npm-publish-computer.yml b/.github/workflows/npm-publish-computer.yml index f8240d64..5029cf38 100644 --- a/.github/workflows/npm-publish-computer.yml +++ b/.github/workflows/npm-publish-computer.yml @@ -24,7 +24,6 @@ jobs: uses: EndBug/version-check@v2 with: file-name: libs/typescript/computer/package.json - static-checking: localIsNew diff-search: true - name: Install dependencies diff --git a/.github/workflows/npm-publish-core.yml b/.github/workflows/npm-publish-core.yml index 7d75e6c6..1c951f99 100644 --- a/.github/workflows/npm-publish-core.yml +++ b/.github/workflows/npm-publish-core.yml @@ -24,7 +24,6 @@ jobs: uses: EndBug/version-check@v2 with: file-name: libs/typescript/core/package.json - static-checking: localIsNew diff-search: true - name: Install dependencies From 2045a387d89d05e8dd8530e86e3905c64159aeec Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 2 Jul 2025 12:47:05 -0700 Subject: [PATCH 133/141] Bump typescript versions for initial release --- libs/typescript/computer/package.json | 2 +- libs/typescript/core/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/typescript/computer/package.json b/libs/typescript/computer/package.json index 797ecbc3..f4cd7656 100644 --- a/libs/typescript/computer/package.json +++ b/libs/typescript/computer/package.json @@ -1,6 +1,6 @@ { "name": "@trycua/computer", - "version": "0.0.1", + "version": "0.1.0", "packageManager": "pnpm@10.11.0", "description": "Typescript SDK for c/ua computer interaction", "type": "module", diff --git a/libs/typescript/core/package.json b/libs/typescript/core/package.json index 2a03920c..cf19cc15 100644 --- a/libs/typescript/core/package.json +++ b/libs/typescript/core/package.json @@ -1,6 +1,6 @@ { "name": "@trycua/core", - "version": "0.0.1", + "version": "0.1.0", "packageManager": "pnpm@10.11.0", "description": "Typescript SDK for c/ua core", "type": "module", From c119ded3be16a29e4ceb9f481ed06976aef6f970 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 2 Jul 2025 12:53:29 -0700 Subject: [PATCH 134/141] Add pnpm to workflow for typescript libs --- .github/workflows/npm-publish-computer.yml | 5 +++++ .github/workflows/npm-publish-core.yml | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/.github/workflows/npm-publish-computer.yml b/.github/workflows/npm-publish-computer.yml index 5029cf38..0dcd26fc 100644 --- a/.github/workflows/npm-publish-computer.yml +++ b/.github/workflows/npm-publish-computer.yml @@ -19,6 +19,11 @@ jobs: node-version: "24.x" registry-url: "https://registry.npmjs.org" + - name: Setup pnpm 10 + uses: pnpm/action-setup@v4 + with: + version: 10 + - name: Check if version changed id: check-version uses: EndBug/version-check@v2 diff --git a/.github/workflows/npm-publish-core.yml b/.github/workflows/npm-publish-core.yml index 1c951f99..b5f6745d 100644 --- a/.github/workflows/npm-publish-core.yml +++ b/.github/workflows/npm-publish-core.yml @@ -19,6 +19,11 @@ jobs: node-version: "24.x" registry-url: "https://registry.npmjs.org" + - name: Setup pnpm 10 + uses: pnpm/action-setup@v4 + with: + version: 10 + - name: Check if version changed id: check-version uses: EndBug/version-check@v2 From 247a15c1c839657a24c511442ea4af0b781450a3 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 2 Jul 2025 13:10:13 -0700 Subject: [PATCH 135/141] [typescript] Update package.json to trigger release --- libs/typescript/computer/package.json | 2 +- libs/typescript/core/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/typescript/computer/package.json b/libs/typescript/computer/package.json index f4cd7656..308db2e2 100644 --- a/libs/typescript/computer/package.json +++ b/libs/typescript/computer/package.json @@ -38,7 +38,7 @@ "prepublishOnly": "pnpm run build" }, "dependencies": { - "@trycua/core": "link:../core", + "@trycua/core": "^0.1.0", "pino": "^9.7.0", "ws": "^8.18.0" }, diff --git a/libs/typescript/core/package.json b/libs/typescript/core/package.json index cf19cc15..3f38a009 100644 --- a/libs/typescript/core/package.json +++ b/libs/typescript/core/package.json @@ -2,7 +2,7 @@ "name": "@trycua/core", "version": "0.1.0", "packageManager": "pnpm@10.11.0", - "description": "Typescript SDK for c/ua core", + "description": "Typescript SDK for c/ua core.", "type": "module", "license": "MIT", "homepage": "https://github.com/trycua/cua/tree/feature/computer/typescript/libs/typescript/computer", From 036dd10550287e9ba9612214bbc187b491882456 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 2 Jul 2025 13:12:13 -0700 Subject: [PATCH 136/141] [Typescript] Bump version to trigger build --- libs/typescript/computer/package.json | 2 +- libs/typescript/core/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/typescript/computer/package.json b/libs/typescript/computer/package.json index 308db2e2..3da753e1 100644 --- a/libs/typescript/computer/package.json +++ b/libs/typescript/computer/package.json @@ -1,6 +1,6 @@ { "name": "@trycua/computer", - "version": "0.1.0", + "version": "0.1.1", "packageManager": "pnpm@10.11.0", "description": "Typescript SDK for c/ua computer interaction", "type": "module", diff --git a/libs/typescript/core/package.json b/libs/typescript/core/package.json index 3f38a009..00749e0d 100644 --- a/libs/typescript/core/package.json +++ b/libs/typescript/core/package.json @@ -1,6 +1,6 @@ { "name": "@trycua/core", - "version": "0.1.0", + "version": "0.1.1", "packageManager": "pnpm@10.11.0", "description": "Typescript SDK for c/ua core.", "type": "module", From 5c54a9c5f2af467ca739557faf62a4684a7dca88 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 2 Jul 2025 13:16:26 -0700 Subject: [PATCH 137/141] [Typescript] Attempt 3 at successfuly publish --- libs/typescript/computer/package.json | 4 ++-- libs/typescript/core/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/typescript/computer/package.json b/libs/typescript/computer/package.json index 3da753e1..8bb24236 100644 --- a/libs/typescript/computer/package.json +++ b/libs/typescript/computer/package.json @@ -1,6 +1,6 @@ { "name": "@trycua/computer", - "version": "0.1.1", + "version": "0.1.2", "packageManager": "pnpm@10.11.0", "description": "Typescript SDK for c/ua computer interaction", "type": "module", @@ -38,7 +38,7 @@ "prepublishOnly": "pnpm run build" }, "dependencies": { - "@trycua/core": "^0.1.0", + "@trycua/core": "link:../core", "pino": "^9.7.0", "ws": "^8.18.0" }, diff --git a/libs/typescript/core/package.json b/libs/typescript/core/package.json index 00749e0d..7f63ff8f 100644 --- a/libs/typescript/core/package.json +++ b/libs/typescript/core/package.json @@ -1,6 +1,6 @@ { "name": "@trycua/core", - "version": "0.1.1", + "version": "0.1.2", "packageManager": "pnpm@10.11.0", "description": "Typescript SDK for c/ua core.", "type": "module", From 50913687eb9547fa14c2d19751b0d0608a36bbdf Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 2 Jul 2025 13:31:36 -0700 Subject: [PATCH 138/141] [Typescript] Update core readme, remove link: versioning from development, bump version for npm publish. --- libs/typescript/computer/package.json | 4 ++-- libs/typescript/core/README.md | 4 ++-- libs/typescript/core/package.json | 2 +- libs/typescript/pnpm-lock.yaml | 14 ++++++++++++-- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/libs/typescript/computer/package.json b/libs/typescript/computer/package.json index 8bb24236..6264bbad 100644 --- a/libs/typescript/computer/package.json +++ b/libs/typescript/computer/package.json @@ -1,6 +1,6 @@ { "name": "@trycua/computer", - "version": "0.1.2", + "version": "0.1.3", "packageManager": "pnpm@10.11.0", "description": "Typescript SDK for c/ua computer interaction", "type": "module", @@ -38,7 +38,7 @@ "prepublishOnly": "pnpm run build" }, "dependencies": { - "@trycua/core": "link:../core", + "@trycua/core": "^0.1.3", "pino": "^9.7.0", "ws": "^8.18.0" }, diff --git a/libs/typescript/core/README.md b/libs/typescript/core/README.md index a6b568ff..20a77b26 100644 --- a/libs/typescript/core/README.md +++ b/libs/typescript/core/README.md @@ -1,6 +1,6 @@ -# tsdown-starter +# C/ua Core TypeScript Library -A starter for creating a tsdown package. +The core c/ua library with support for telemetry and other utilities. ## Development diff --git a/libs/typescript/core/package.json b/libs/typescript/core/package.json index 7f63ff8f..d15d4138 100644 --- a/libs/typescript/core/package.json +++ b/libs/typescript/core/package.json @@ -1,6 +1,6 @@ { "name": "@trycua/core", - "version": "0.1.2", + "version": "0.1.3", "packageManager": "pnpm@10.11.0", "description": "Typescript SDK for c/ua core.", "type": "module", diff --git a/libs/typescript/pnpm-lock.yaml b/libs/typescript/pnpm-lock.yaml index 0050b9b6..99485af8 100644 --- a/libs/typescript/pnpm-lock.yaml +++ b/libs/typescript/pnpm-lock.yaml @@ -15,8 +15,8 @@ importers: computer: dependencies: '@trycua/core': - specifier: link:../core - version: link:../core + specifier: ^0.1.2 + version: 0.1.2 pino: specifier: ^9.7.0 version: 9.7.0 @@ -516,6 +516,9 @@ packages: cpu: [x64] os: [win32] + '@trycua/core@0.1.2': + resolution: {integrity: sha512-pSQZaR46OG3MtUCBaneG6RpJD1xfX754VDZ101FM5tkUUiymIrxpQicQEUfhwEBxbI/EmBnmCnVY1AFKvykKzQ==} + '@tybys/wasm-util@0.9.0': resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} @@ -1339,6 +1342,13 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.44.1': optional: true + '@trycua/core@0.1.2': + dependencies: + '@types/uuid': 10.0.0 + pino: 9.7.0 + posthog-node: 5.1.1 + uuid: 11.1.0 + '@tybys/wasm-util@0.9.0': dependencies: tslib: 2.8.1 From 645171e7aba1104c15125fe4803c61ae2c05ba01 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 2 Jul 2025 13:34:54 -0700 Subject: [PATCH 139/141] [Typescript] Temporarily force publish for all pushes until bugs are fixed. --- .github/workflows/npm-publish-computer.yml | 3 --- .github/workflows/npm-publish-core.yml | 3 --- libs/typescript/computer/package.json | 2 +- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/npm-publish-computer.yml b/.github/workflows/npm-publish-computer.yml index 0dcd26fc..aa961165 100644 --- a/.github/workflows/npm-publish-computer.yml +++ b/.github/workflows/npm-publish-computer.yml @@ -32,17 +32,14 @@ jobs: diff-search: true - name: Install dependencies - if: steps.check-version.outputs.changed == 'true' working-directory: ./libs/typescript/computer run: pnpm install --frozen-lockfile - name: Build package - if: steps.check-version.outputs.changed == 'true' working-directory: ./libs/typescript/computer run: pnpm run build --if-present - name: Publish to npm - if: steps.check-version.outputs.changed == 'true' working-directory: ./libs/typescript/computer run: pnpm publish --access public --no-git-checks env: diff --git a/.github/workflows/npm-publish-core.yml b/.github/workflows/npm-publish-core.yml index b5f6745d..f7ff6ead 100644 --- a/.github/workflows/npm-publish-core.yml +++ b/.github/workflows/npm-publish-core.yml @@ -32,17 +32,14 @@ jobs: diff-search: true - name: Install dependencies - if: steps.check-version.outputs.changed == 'true' working-directory: ./libs/typescript/core run: pnpm install --frozen-lockfile - name: Build package - if: steps.check-version.outputs.changed == 'true' working-directory: ./libs/typescript/core run: pnpm run build --if-present - name: Publish to npm - if: steps.check-version.outputs.changed == 'true' working-directory: ./libs/typescript/core run: pnpm publish --access public --no-git-checks env: diff --git a/libs/typescript/computer/package.json b/libs/typescript/computer/package.json index 6264bbad..ba9c0934 100644 --- a/libs/typescript/computer/package.json +++ b/libs/typescript/computer/package.json @@ -38,7 +38,7 @@ "prepublishOnly": "pnpm run build" }, "dependencies": { - "@trycua/core": "^0.1.3", + "@trycua/core": "^0.1.2", "pino": "^9.7.0", "ws": "^8.18.0" }, From 0415741f8cc7303a41b25bb17c770fa4b7bc8230 Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 2 Jul 2025 13:38:47 -0700 Subject: [PATCH 140/141] Update computer example to use @trycua/computer from npm --- examples/computer-example-ts/package.json | 4 +- examples/computer-example-ts/pnpm-lock.yaml | 161 +++++++++++++++++++- 2 files changed, 155 insertions(+), 10 deletions(-) diff --git a/examples/computer-example-ts/package.json b/examples/computer-example-ts/package.json index 4e961528..65210e18 100644 --- a/examples/computer-example-ts/package.json +++ b/examples/computer-example-ts/package.json @@ -1,5 +1,5 @@ { - "name": "cua-cloud-openai", + "name": "computer-example-ts", "version": "1.0.0", "description": "", "type": "module", @@ -13,7 +13,7 @@ "license": "MIT", "packageManager": "pnpm@10.12.3", "dependencies": { - "@trycua/computer": "link:../../libs/typescript/computer", + "@trycua/computer": "^0.1.3", "dotenv": "^16.5.0", "openai": "^5.7.0" }, diff --git a/examples/computer-example-ts/pnpm-lock.yaml b/examples/computer-example-ts/pnpm-lock.yaml index 6d0882b3..f0752379 100644 --- a/examples/computer-example-ts/pnpm-lock.yaml +++ b/examples/computer-example-ts/pnpm-lock.yaml @@ -9,18 +9,18 @@ importers: .: dependencies: '@trycua/computer': - specifier: link:../../libs/typescript/computer - version: link:../../libs/typescript/computer + specifier: ^0.1.3 + version: 0.1.3 dotenv: specifier: ^16.5.0 version: 16.6.1 openai: specifier: ^5.7.0 - version: 5.8.2 + version: 5.8.2(ws@8.18.3) devDependencies: '@types/node': specifier: ^22.15.33 - version: 22.15.34 + version: 22.16.0 tsx: specifier: ^4.20.3 version: 4.20.3 @@ -180,8 +180,21 @@ packages: cpu: [x64] os: [win32] - '@types/node@22.15.34': - resolution: {integrity: sha512-8Y6E5WUupYy1Dd0II32BsWAx5MWdcnRd8L84Oys3veg1YrYtNtzgO4CFhiBg6MDSjk7Ay36HYOnU7/tuOzIzcw==} + '@trycua/computer@0.1.3': + resolution: {integrity: sha512-RTDgULV6wQJuTsiwhei9aQO6YQSM1TBQqOCDUPHUbTIjtRqzMvMdwtcKAKxZZptzJcBX14bWtbucY65Wu6IEFg==} + + '@trycua/core@0.1.3': + resolution: {integrity: sha512-sv7BEajJyZ+JNxrOdhao4qCOtRrh+S0XYf64ehAT4UAhLC73Kep06bGa/Uel0Ow5xGXXrg0aiVBL7zO9+/w4/Q==} + + '@types/node@22.16.0': + resolution: {integrity: sha512-B2egV9wALML1JCpv3VQoQ+yesQKAmNMBIAY7OteVrikcOcAkWm+dGL6qpeCktPjAv6N1JLnhbNiqS35UpFyBsQ==} + + '@types/uuid@10.0.0': + resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} dotenv@16.6.1: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} @@ -192,6 +205,10 @@ packages: engines: {node: '>=18'} hasBin: true + fast-redact@3.5.0: + resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} + engines: {node: '>=6'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -200,6 +217,10 @@ packages: get-tsconfig@4.10.1: resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + openai@5.8.2: resolution: {integrity: sha512-8C+nzoHYgyYOXhHGN6r0fcb4SznuEn1R7YZMvlqDbnCuE0FM2mm3T1HiYW6WIcMS/F1Of2up/cSPjLPaWt0X9Q==} hasBin: true @@ -212,9 +233,47 @@ packages: zod: optional: true + pino-abstract-transport@2.0.0: + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} + + pino-std-serializers@7.0.0: + resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} + + pino@9.7.0: + resolution: {integrity: sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==} + hasBin: true + + posthog-node@5.1.1: + resolution: {integrity: sha512-6VISkNdxO24ehXiDA4dugyCSIV7lpGVaEu5kn/dlAj+SJ1lgcDru9PQ8p/+GSXsXVxohd1t7kHL2JKc9NoGb0w==} + engines: {node: '>=20'} + + process-warning@5.0.0: + resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} + + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + sonic-boom@4.2.0: + resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + thread-stream@3.1.0: + resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + tsx@4.20.3: resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==} engines: {node: '>=18.0.0'} @@ -228,6 +287,22 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + snapshots: '@esbuild/aix-ppc64@0.25.5': @@ -305,10 +380,30 @@ snapshots: '@esbuild/win32-x64@0.25.5': optional: true - '@types/node@22.15.34': + '@trycua/computer@0.1.3': + dependencies: + '@trycua/core': 0.1.3 + pino: 9.7.0 + ws: 8.18.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@trycua/core@0.1.3': + dependencies: + '@types/uuid': 10.0.0 + pino: 9.7.0 + posthog-node: 5.1.1 + uuid: 11.1.0 + + '@types/node@22.16.0': dependencies: undici-types: 6.21.0 + '@types/uuid@10.0.0': {} + + atomic-sleep@1.0.0: {} + dotenv@16.6.1: {} esbuild@0.25.5: @@ -339,6 +434,8 @@ snapshots: '@esbuild/win32-ia32': 0.25.5 '@esbuild/win32-x64': 0.25.5 + fast-redact@3.5.0: {} + fsevents@2.3.3: optional: true @@ -346,10 +443,54 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 - openai@5.8.2: {} + on-exit-leak-free@2.1.2: {} + + openai@5.8.2(ws@8.18.3): + optionalDependencies: + ws: 8.18.3 + + pino-abstract-transport@2.0.0: + dependencies: + split2: 4.2.0 + + pino-std-serializers@7.0.0: {} + + pino@9.7.0: + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.5.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pino-std-serializers: 7.0.0 + process-warning: 5.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.0 + thread-stream: 3.1.0 + + posthog-node@5.1.1: {} + + process-warning@5.0.0: {} + + quick-format-unescaped@4.0.4: {} + + real-require@0.2.0: {} resolve-pkg-maps@1.0.0: {} + safe-stable-stringify@2.5.0: {} + + sonic-boom@4.2.0: + dependencies: + atomic-sleep: 1.0.0 + + split2@4.2.0: {} + + thread-stream@3.1.0: + dependencies: + real-require: 0.2.0 + tsx@4.20.3: dependencies: esbuild: 0.25.5 @@ -360,3 +501,7 @@ snapshots: typescript@5.8.3: {} undici-types@6.21.0: {} + + uuid@11.1.0: {} + + ws@8.18.3: {} From aa691c5fa4f9a6ff4f35d7ee4eb254cf018095fb Mon Sep 17 00:00:00 2001 From: Morgan Dean Date: Wed, 2 Jul 2025 13:40:09 -0700 Subject: [PATCH 141/141] Re-enable version-based publishing to npm --- .github/workflows/npm-publish-computer.yml | 3 +++ .github/workflows/npm-publish-core.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/npm-publish-computer.yml b/.github/workflows/npm-publish-computer.yml index aa961165..0dcd26fc 100644 --- a/.github/workflows/npm-publish-computer.yml +++ b/.github/workflows/npm-publish-computer.yml @@ -32,14 +32,17 @@ jobs: diff-search: true - name: Install dependencies + if: steps.check-version.outputs.changed == 'true' working-directory: ./libs/typescript/computer run: pnpm install --frozen-lockfile - name: Build package + if: steps.check-version.outputs.changed == 'true' working-directory: ./libs/typescript/computer run: pnpm run build --if-present - name: Publish to npm + if: steps.check-version.outputs.changed == 'true' working-directory: ./libs/typescript/computer run: pnpm publish --access public --no-git-checks env: diff --git a/.github/workflows/npm-publish-core.yml b/.github/workflows/npm-publish-core.yml index f7ff6ead..b5f6745d 100644 --- a/.github/workflows/npm-publish-core.yml +++ b/.github/workflows/npm-publish-core.yml @@ -32,14 +32,17 @@ jobs: diff-search: true - name: Install dependencies + if: steps.check-version.outputs.changed == 'true' working-directory: ./libs/typescript/core run: pnpm install --frozen-lockfile - name: Build package + if: steps.check-version.outputs.changed == 'true' working-directory: ./libs/typescript/core run: pnpm run build --if-present - name: Publish to npm + if: steps.check-version.outputs.changed == 'true' working-directory: ./libs/typescript/core run: pnpm publish --access public --no-git-checks env: