mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-19 13:33:35 +08:00
feat: enable Computer Use with Python bridge replacing private native modules
Bypass all three gating layers (compile-time feature flag, subscription check, GrowthBook remote config) and replace Anthropic's private native modules (@ant/computer-use-swift, @ant/computer-use-input) with a Python bridge using pyautogui + mss + pyobjc. Works on any macOS with any Anthropic-protocol compatible model.
This commit is contained in:
parent
1d622ecee2
commit
e8f42840a2
4
.gitignore
vendored
4
.gitignore
vendored
@ -2,3 +2,7 @@
|
|||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
|
# Computer Use runtime (auto-generated)
|
||||||
|
.runtime/
|
||||||
|
extracted-natives/
|
||||||
|
|||||||
21
README.en.md
21
README.en.md
@ -17,6 +17,7 @@ A **locally runnable version** repaired from the leaked Claude Code source, with
|
|||||||
- [Quick Start](#quick-start)
|
- [Quick Start](#quick-start)
|
||||||
- [Environment Variables](#environment-variables)
|
- [Environment Variables](#environment-variables)
|
||||||
- [Fallback Mode](#fallback-mode)
|
- [Fallback Mode](#fallback-mode)
|
||||||
|
- [Computer Use Desktop Control](#computer-use-desktop-control)
|
||||||
- [FAQ](#faq)
|
- [FAQ](#faq)
|
||||||
- [Fixes Compared with the Original Leaked Source](#fixes-compared-with-the-original-leaked-source)
|
- [Fixes Compared with the Original Leaked Source](#fixes-compared-with-the-original-leaked-source)
|
||||||
- [Project Structure](#project-structure)
|
- [Project Structure](#project-structure)
|
||||||
@ -30,8 +31,11 @@ A **locally runnable version** repaired from the leaked Claude Code source, with
|
|||||||
- `--print` headless mode for scripts and CI
|
- `--print` headless mode for scripts and CI
|
||||||
- MCP server, plugin, and Skills support
|
- MCP server, plugin, and Skills support
|
||||||
- Custom API endpoint and model support ([Third-Party Models Guide](docs/third-party-models.en.md))
|
- Custom API endpoint and model support ([Third-Party Models Guide](docs/third-party-models.en.md))
|
||||||
|
- **Computer Use desktop control** (screenshots, mouse, keyboard, app management) — [Guide](docs/computer-use.en.md)
|
||||||
- Fallback Recovery CLI mode
|
- Fallback Recovery CLI mode
|
||||||
|
|
||||||
|
> **Computer Use Note**: This project includes a **modified version of Computer Use**. The official implementation relies on Anthropic's private native modules. We replaced the entire underlying operation layer with a Python bridge (`pyautogui` + `mss` + `pyobjc`), enabling anyone to use Computer Use on macOS. See the [Computer Use Guide](docs/computer-use.en.md) for details.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Architecture Overview
|
## Architecture Overview
|
||||||
@ -214,6 +218,23 @@ CLAUDE_CODE_FORCE_RECOVERY_CLI=1 ./bin/claude-haha
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Computer Use Desktop Control
|
||||||
|
|
||||||
|
This project enables and modifies Claude Code's Computer Use feature (internal codename "Chicago"), allowing AI models to directly control your macOS desktop — screenshots, mouse clicks, keyboard input, app management.
|
||||||
|
|
||||||
|
**Underlying modification**: The official implementation depends on Anthropic's private native modules (`@ant/computer-use-swift`, `@ant/computer-use-input`). This project replaces them entirely with a Python bridge using `pyautogui` (mouse/keyboard), `mss` (screenshots), and `pyobjc` (macOS APIs) — no closed-source binaries required.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ensure Python 3 and macOS Accessibility/Screen Recording permissions, then:
|
||||||
|
./bin/claude-haha
|
||||||
|
> Take a screenshot
|
||||||
|
> Open Safari and search for something
|
||||||
|
```
|
||||||
|
|
||||||
|
For supported platforms, technical architecture, and approaches we tried, see: **[Computer Use Guide](docs/computer-use.en.md)**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Fixes Compared with the Original Leaked Source
|
## Fixes Compared with the Original Leaked Source
|
||||||
|
|
||||||
The leaked source could not run directly. This repository mainly fixes the following issues:
|
The leaked source could not run directly. This repository mainly fixes the following issues:
|
||||||
|
|||||||
21
README.md
21
README.md
@ -17,6 +17,7 @@
|
|||||||
- [快速开始](#快速开始)
|
- [快速开始](#快速开始)
|
||||||
- [环境变量说明](#环境变量说明)
|
- [环境变量说明](#环境变量说明)
|
||||||
- [降级模式](#降级模式)
|
- [降级模式](#降级模式)
|
||||||
|
- [Computer Use 桌面控制](#computer-use-桌面控制)
|
||||||
- [常见问题](#常见问题)
|
- [常见问题](#常见问题)
|
||||||
- [相对于原始泄露源码的修复](#相对于原始泄露源码的修复)
|
- [相对于原始泄露源码的修复](#相对于原始泄露源码的修复)
|
||||||
- [项目结构](#项目结构)
|
- [项目结构](#项目结构)
|
||||||
@ -30,8 +31,11 @@
|
|||||||
- `--print` 无头模式(脚本/CI 场景)
|
- `--print` 无头模式(脚本/CI 场景)
|
||||||
- 支持 MCP 服务器、插件、Skills
|
- 支持 MCP 服务器、插件、Skills
|
||||||
- 支持自定义 API 端点和模型([第三方模型使用指南](docs/third-party-models.md))
|
- 支持自定义 API 端点和模型([第三方模型使用指南](docs/third-party-models.md))
|
||||||
|
- **Computer Use 桌面控制**(截屏、鼠标、键盘、应用管理)— [使用指南](docs/computer-use.md)
|
||||||
- 降级 Recovery CLI 模式
|
- 降级 Recovery CLI 模式
|
||||||
|
|
||||||
|
> **Computer Use 说明**:本项目包含**魔改版的 Computer Use** 功能。官方实现依赖 Anthropic 私有原生模块,我们替换了整个底层操作层,使用 Python bridge(`pyautogui` + `mss` + `pyobjc`)实现,使得任何人都可以在 macOS 上使用。详见 [Computer Use 功能指南](docs/computer-use.md)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 架构概览
|
## 架构概览
|
||||||
@ -214,6 +218,23 @@ CLAUDE_CODE_FORCE_RECOVERY_CLI=1 ./bin/claude-haha
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Computer Use 桌面控制
|
||||||
|
|
||||||
|
本项目启用并改造了 Claude Code 的 Computer Use 功能(内部代号 "Chicago"),让 AI 模型可以直接控制你的 macOS 桌面——截屏、鼠标点击、键盘输入、应用管理。
|
||||||
|
|
||||||
|
**底层改造**:官方实现依赖 Anthropic 私有原生模块(`@ant/computer-use-swift`、`@ant/computer-use-input`),本项目用 Python bridge 完全替代,使用 `pyautogui`(鼠标键盘)、`mss`(截图)、`pyobjc`(macOS API),无需任何闭源二进制。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 确保有 Python 3 和 macOS 辅助功能/屏幕录制权限,然后直接使用:
|
||||||
|
./bin/claude-haha
|
||||||
|
> 帮我截个屏
|
||||||
|
> 打开网易云音乐搜索一首歌
|
||||||
|
```
|
||||||
|
|
||||||
|
详细说明、支持的设备列表、技术架构和尝试过的方案请参考:**[Computer Use 功能指南](docs/computer-use.md)**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 相对于原始泄露源码的修复
|
## 相对于原始泄露源码的修复
|
||||||
|
|
||||||
泄露的源码无法直接运行,主要修复了以下问题:
|
泄露的源码无法直接运行,主要修复了以下问题:
|
||||||
|
|||||||
816
bun.lock
Normal file
816
bun.lock
Normal file
@ -0,0 +1,816 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"configVersion": 0,
|
||||||
|
"workspaces": {
|
||||||
|
"": {
|
||||||
|
"name": "claude-code-local",
|
||||||
|
"dependencies": {
|
||||||
|
"@anthropic-ai/sandbox-runtime": "^0.0.44",
|
||||||
|
"@anthropic-ai/sdk": "^0.80.0",
|
||||||
|
"@aws-sdk/client-bedrock-runtime": "^3.1020.0",
|
||||||
|
"@commander-js/extra-typings": "^14.0.0",
|
||||||
|
"@growthbook/growthbook": "^1.6.5",
|
||||||
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
||||||
|
"@opentelemetry/api-logs": "^0.214.0",
|
||||||
|
"@opentelemetry/core": "^2.6.1",
|
||||||
|
"@opentelemetry/resources": "^2.6.1",
|
||||||
|
"@opentelemetry/sdk-logs": "^0.214.0",
|
||||||
|
"@opentelemetry/sdk-metrics": "^2.6.1",
|
||||||
|
"@opentelemetry/sdk-trace-base": "^2.6.1",
|
||||||
|
"@opentelemetry/semantic-conventions": "^1.40.0",
|
||||||
|
"ajv": "^8.18.0",
|
||||||
|
"asciichart": "^1.5.25",
|
||||||
|
"auto-bind": "^5.0.1",
|
||||||
|
"axios": "^1.14.0",
|
||||||
|
"bidi-js": "^1.0.3",
|
||||||
|
"chalk": "^5.6.2",
|
||||||
|
"chokidar": "^5.0.0",
|
||||||
|
"cli-boxes": "^4.0.1",
|
||||||
|
"code-excerpt": "^4.0.0",
|
||||||
|
"diff": "^8.0.4",
|
||||||
|
"emoji-regex": "^10.6.0",
|
||||||
|
"env-paths": "^4.0.0",
|
||||||
|
"execa": "^9.6.1",
|
||||||
|
"figures": "^6.1.0",
|
||||||
|
"fuse.js": "^7.1.0",
|
||||||
|
"get-east-asian-width": "^1.5.0",
|
||||||
|
"google-auth-library": "^10.6.2",
|
||||||
|
"highlight.js": "^11.11.1",
|
||||||
|
"https-proxy-agent": "^8.0.0",
|
||||||
|
"ignore": "^7.0.5",
|
||||||
|
"indent-string": "^5.0.0",
|
||||||
|
"ink": "^6.8.0",
|
||||||
|
"jsonc-parser": "^3.3.1",
|
||||||
|
"lodash-es": "^4.17.23",
|
||||||
|
"lru-cache": "^11.2.7",
|
||||||
|
"marked": "^17.0.5",
|
||||||
|
"p-map": "^7.0.4",
|
||||||
|
"picomatch": "^4.0.4",
|
||||||
|
"proper-lockfile": "^4.1.2",
|
||||||
|
"qrcode": "^1.5.4",
|
||||||
|
"react": "^19.2.4",
|
||||||
|
"react-reconciler": "^0.33.0",
|
||||||
|
"semver": "^7.7.4",
|
||||||
|
"shell-quote": "^1.8.3",
|
||||||
|
"signal-exit": "^4.1.0",
|
||||||
|
"stack-utils": "^2.0.6",
|
||||||
|
"strip-ansi": "^7.2.0",
|
||||||
|
"supports-hyperlinks": "^4.4.0",
|
||||||
|
"tree-kill": "^1.2.2",
|
||||||
|
"type-fest": "^5.5.0",
|
||||||
|
"undici": "^7.24.6",
|
||||||
|
"usehooks-ts": "^3.1.1",
|
||||||
|
"vscode-jsonrpc": "^8.2.1",
|
||||||
|
"vscode-languageserver-types": "^3.17.5",
|
||||||
|
"wrap-ansi": "^10.0.0",
|
||||||
|
"ws": "^8.20.0",
|
||||||
|
"xss": "^1.0.15",
|
||||||
|
"yaml": "^2.8.3",
|
||||||
|
"zod": "^4.3.6",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages/computer-use-input": {
|
||||||
|
"name": "@ant/computer-use-input",
|
||||||
|
"version": "0.1.0",
|
||||||
|
},
|
||||||
|
"packages/computer-use-swift": {
|
||||||
|
"name": "@ant/computer-use-swift",
|
||||||
|
"version": "0.1.0",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.5", "https://registry.npmmirror.com/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.5.tgz", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw=="],
|
||||||
|
|
||||||
|
"@ant/computer-use-input": ["@ant/computer-use-input@workspace:packages/computer-use-input"],
|
||||||
|
|
||||||
|
"@ant/computer-use-swift": ["@ant/computer-use-swift@workspace:packages/computer-use-swift"],
|
||||||
|
|
||||||
|
"@anthropic-ai/sandbox-runtime": ["@anthropic-ai/sandbox-runtime@0.0.44", "https://registry.npmmirror.com/@anthropic-ai/sandbox-runtime/-/sandbox-runtime-0.0.44.tgz", { "dependencies": { "@pondwader/socks5-server": "^1.0.10", "@types/lodash-es": "^4.17.12", "commander": "^12.1.0", "lodash-es": "^4.17.23", "shell-quote": "^1.8.3", "zod": "^3.24.1" }, "bin": { "srt": "dist/cli.js" } }, "sha512-mmyjq0mzsHnQZyiU+FGYyaiJcPckuQpP78VB8iqFi2IOu8rcb9i5SmaOKyJENJNfY8l/1grzLMQgWq4Apvmozw=="],
|
||||||
|
|
||||||
|
"@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.80.0", "https://registry.npmmirror.com/@anthropic-ai/sdk/-/sdk-0.80.0.tgz", { "dependencies": { "json-schema-to-ts": "^3.1.1" }, "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" }, "bin": { "anthropic-ai-sdk": "bin/cli" } }, "sha512-WeXLn7zNVk3yjeshn+xZHvld6AoFUOR3Sep6pSoHho5YbSi6HwcirqgPA5ccFuW8QTVJAAU7N8uQQC6Wa9TG+g=="],
|
||||||
|
|
||||||
|
"@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "https://registry.npmmirror.com/@aws-crypto/crc32/-/crc32-5.2.0.tgz", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="],
|
||||||
|
|
||||||
|
"@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "https://registry.npmmirror.com/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="],
|
||||||
|
|
||||||
|
"@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "https://registry.npmmirror.com/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="],
|
||||||
|
|
||||||
|
"@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "https://registry.npmmirror.com/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="],
|
||||||
|
|
||||||
|
"@aws-crypto/util": ["@aws-crypto/util@5.2.0", "https://registry.npmmirror.com/@aws-crypto/util/-/util-5.2.0.tgz", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="],
|
||||||
|
|
||||||
|
"@aws-sdk/client-bedrock-runtime": ["@aws-sdk/client-bedrock-runtime@3.1020.0", "https://registry.npmmirror.com/@aws-sdk/client-bedrock-runtime/-/client-bedrock-runtime-3.1020.0.tgz", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.26", "@aws-sdk/credential-provider-node": "^3.972.28", "@aws-sdk/eventstream-handler-node": "^3.972.12", "@aws-sdk/middleware-eventstream": "^3.972.8", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.9", "@aws-sdk/middleware-user-agent": "^3.972.27", "@aws-sdk/middleware-websocket": "^3.972.14", "@aws-sdk/region-config-resolver": "^3.972.10", "@aws-sdk/token-providers": "3.1020.0", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.13", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.13", "@smithy/eventstream-serde-browser": "^4.2.12", "@smithy/eventstream-serde-config-resolver": "^4.3.12", "@smithy/eventstream-serde-node": "^4.2.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.28", "@smithy/middleware-retry": "^4.4.45", "@smithy/middleware-serde": "^4.2.16", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.1", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.8", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.44", "@smithy/util-defaults-mode-node": "^4.2.48", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-stream": "^4.5.21", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-nqDCbaB05gRc3FuIEN74Mo04+k8RNI0YT2YBAU/9nioqgDyoqzMx8Ia2QWaw9UhUyIHMBjcFEfKIPfCZx7caCw=="],
|
||||||
|
|
||||||
|
"@aws-sdk/core": ["@aws-sdk/core@3.973.26", "https://registry.npmmirror.com/@aws-sdk/core/-/core-3.973.26.tgz", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/xml-builder": "^3.972.16", "@smithy/core": "^3.23.13", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/smithy-client": "^4.12.8", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-A/E6n2W42ruU+sfWk+mMUOyVXbsSgGrY3MJ9/0Az5qUdG67y8I6HYzzoAa+e/lzxxl1uCYmEL6BTMi9ZiZnplQ=="],
|
||||||
|
|
||||||
|
"@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.24", "https://registry.npmmirror.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.24.tgz", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-FWg8uFmT6vQM7VuzELzwVo5bzExGaKHdubn0StjgrcU5FvuLExUe+k06kn/40uKv59rYzhez8eFNM4yYE/Yb/w=="],
|
||||||
|
|
||||||
|
"@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.26", "https://registry.npmmirror.com/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.26.tgz", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/types": "^3.973.6", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.1", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.8", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.21", "tslib": "^2.6.2" } }, "sha512-CY4ppZ+qHYqcXqBVi//sdHST1QK3KzOEiLtpLsc9W2k2vfZPKExGaQIsOwcyvjpjUEolotitmd3mUNY56IwDEA=="],
|
||||||
|
|
||||||
|
"@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.27", "https://registry.npmmirror.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.27.tgz", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/credential-provider-env": "^3.972.24", "@aws-sdk/credential-provider-http": "^3.972.26", "@aws-sdk/credential-provider-login": "^3.972.27", "@aws-sdk/credential-provider-process": "^3.972.24", "@aws-sdk/credential-provider-sso": "^3.972.27", "@aws-sdk/credential-provider-web-identity": "^3.972.27", "@aws-sdk/nested-clients": "^3.996.17", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Um26EsNSUfVUX0wUXnUA1W3wzKhVy6nviEElsh5lLZUYj9bk6DXOPnpte0gt+WHubcVfVsRk40bbm4KaroTEag=="],
|
||||||
|
|
||||||
|
"@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.27", "https://registry.npmmirror.com/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.27.tgz", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/nested-clients": "^3.996.17", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-t3ehEtHomGZwg5Gixw4fYbYtG9JBnjfAjSDabxhPEu/KLLUp0BB37/APX7MSKXQhX6ZH7pseuACFJ19NrAkNdg=="],
|
||||||
|
|
||||||
|
"@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.28", "https://registry.npmmirror.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.28.tgz", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.24", "@aws-sdk/credential-provider-http": "^3.972.26", "@aws-sdk/credential-provider-ini": "^3.972.27", "@aws-sdk/credential-provider-process": "^3.972.24", "@aws-sdk/credential-provider-sso": "^3.972.27", "@aws-sdk/credential-provider-web-identity": "^3.972.27", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-rren+P6k5rShG5PX61iVi40kKdueyuMLBRTctQbyR5LooO9Ygr5L6R7ilG7RF1957NSH3KC3TU206fZuKwjSpQ=="],
|
||||||
|
|
||||||
|
"@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.24", "https://registry.npmmirror.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.24.tgz", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Q2k/XLrFXhEztPHqj4SLCNID3hEPdlhh1CDLBpNnM+1L8fq7P+yON9/9M1IGN/dA5W45v44ylERfXtDAlmMNmw=="],
|
||||||
|
|
||||||
|
"@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.27", "https://registry.npmmirror.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.27.tgz", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/nested-clients": "^3.996.17", "@aws-sdk/token-providers": "3.1020.0", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-CWXeGjlbBuHcm9appZUgXKP2zHDyTti0/+gXpSFJ2J3CnSwf1KWjicjN0qG2ozkMH6blrrzMrimeIOEYNl238Q=="],
|
||||||
|
|
||||||
|
"@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.27", "https://registry.npmmirror.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.27.tgz", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/nested-clients": "^3.996.17", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-CUY4hQIFswdQNEsRGEzGBUKGMK5KpqmNDdu2ROMgI+45PLFS8H0y3Tm7kvM16uvvw3n1pVxk85tnRVUTgtaa1w=="],
|
||||||
|
|
||||||
|
"@aws-sdk/eventstream-handler-node": ["@aws-sdk/eventstream-handler-node@3.972.12", "https://registry.npmmirror.com/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.12.tgz", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/eventstream-codec": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ruyc/MNR6e+cUrGCth7fLQ12RXBZDy/bV06tgqB9Z5n/0SN/C0m6bsQEV8FF9zPI6VSAOaRd0rNgmpYVnGawrQ=="],
|
||||||
|
|
||||||
|
"@aws-sdk/middleware-eventstream": ["@aws-sdk/middleware-eventstream@3.972.8", "https://registry.npmmirror.com/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.8.tgz", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-r+oP+tbCxgqXVC3pu3MUVePgSY0ILMjA+aEwOosS77m3/DRbtvHrHwqvMcw+cjANMeGzJ+i0ar+n77KXpRA8RQ=="],
|
||||||
|
|
||||||
|
"@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.8", "https://registry.npmmirror.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.8.tgz", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ=="],
|
||||||
|
|
||||||
|
"@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.8", "https://registry.npmmirror.com/@aws-sdk/middleware-logger/-/middleware-logger-3.972.8.tgz", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-CWl5UCM57WUFaFi5kB7IBY1UmOeLvNZAZ2/OZ5l20ldiJ3TiIz1pC65gYj8X0BCPWkeR1E32mpsCk1L1I4n+lA=="],
|
||||||
|
|
||||||
|
"@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.9", "https://registry.npmmirror.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.9.tgz", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-/Wt5+CT8dpTFQxEJ9iGy/UGrXr7p2wlIOEHvIr/YcHYByzoLjrqkYqXdJjd9UIgWjv7eqV2HnFJen93UTuwfTQ=="],
|
||||||
|
|
||||||
|
"@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.27", "https://registry.npmmirror.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.27.tgz", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@smithy/core": "^3.23.13", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-retry": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-TIRLO5UR2+FVUGmhYoAwVkKhcVzywEDX/5LzR9tjy1h8FQAXOtFg2IqgmwvxU7y933rkTn9rl6AdgcAUgQ1/Kg=="],
|
||||||
|
|
||||||
|
"@aws-sdk/middleware-websocket": ["@aws-sdk/middleware-websocket@3.972.14", "https://registry.npmmirror.com/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.14.tgz", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-format-url": "^3.972.8", "@smithy/eventstream-codec": "^4.2.12", "@smithy/eventstream-serde-browser": "^4.2.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-qnfDlIHjm6DrTYNvWOUbnZdVKgtoKbO/Qzj+C0Wp5Y7VUrsvBRQtGKxD+hc+mRTS4N0kBJ6iZ3+zxm4N1OSyjg=="],
|
||||||
|
|
||||||
|
"@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.17", "https://registry.npmmirror.com/@aws-sdk/nested-clients/-/nested-clients-3.996.17.tgz", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.26", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.9", "@aws-sdk/middleware-user-agent": "^3.972.27", "@aws-sdk/region-config-resolver": "^3.972.10", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.13", "@smithy/config-resolver": "^4.4.13", "@smithy/core": "^3.23.13", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.28", "@smithy/middleware-retry": "^4.4.45", "@smithy/middleware-serde": "^4.2.16", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.1", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.8", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.44", "@smithy/util-defaults-mode-node": "^4.2.48", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-7B0HIX0tEFmOSJuWzdHZj1WhMXSryM+h66h96ZkqSncoY7J6wq61KOu4Kr57b/YnJP3J/EeQYVFulgR281h+7A=="],
|
||||||
|
|
||||||
|
"@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.10", "https://registry.npmmirror.com/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.10.tgz", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/config-resolver": "^4.4.13", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-1dq9ToC6e070QvnVhhbAs3bb5r6cQ10gTVc6cyRV5uvQe7P138TV2uG2i6+Yok4bAkVAcx5AqkTEBUvWEtBlsQ=="],
|
||||||
|
|
||||||
|
"@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1020.0", "https://registry.npmmirror.com/@aws-sdk/token-providers/-/token-providers-3.1020.0.tgz", { "dependencies": { "@aws-sdk/core": "^3.973.26", "@aws-sdk/nested-clients": "^3.996.17", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-T61KA/VKl0zVUubdxigr1ut7SEpwE1/4CIKb14JDLyTAOne2yWKtQE1dDCSHl0UqrZNwW/bTt+EBHfQbslZJdw=="],
|
||||||
|
|
||||||
|
"@aws-sdk/types": ["@aws-sdk/types@3.973.6", "https://registry.npmmirror.com/@aws-sdk/types/-/types-3.973.6.tgz", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="],
|
||||||
|
|
||||||
|
"@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.5", "https://registry.npmmirror.com/@aws-sdk/util-endpoints/-/util-endpoints-3.996.5.tgz", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-endpoints": "^3.3.3", "tslib": "^2.6.2" } }, "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw=="],
|
||||||
|
|
||||||
|
"@aws-sdk/util-format-url": ["@aws-sdk/util-format-url@3.972.8", "https://registry.npmmirror.com/@aws-sdk/util-format-url/-/util-format-url-3.972.8.tgz", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-J6DS9oocrgxM8xlUTTmQOuwRF6rnAGEujAN9SAzllcrQmwn5iJ58ogxy3SEhD0Q7JZvlA5jvIXBkpQRqEqlE9A=="],
|
||||||
|
|
||||||
|
"@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.5", "https://registry.npmmirror.com/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ=="],
|
||||||
|
|
||||||
|
"@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.8", "https://registry.npmmirror.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.8.tgz", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-B3KGXJviV2u6Cdw2SDY2aDhoJkVfY/Q/Trwk2CMSkikE1Oi6gRzxhvhIfiRpHfmIsAhV4EA54TVEX8K6CbHbkA=="],
|
||||||
|
|
||||||
|
"@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.13", "https://registry.npmmirror.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.973.13.tgz", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.27", "@aws-sdk/types": "^3.973.6", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-s1dCJ0J9WU9UPkT3FFqhKTSquYTkqWXGRaapHFyWwwJH86ZussewhNST5R5TwXVL1VSHq4aJVl9fWK+svaRVCQ=="],
|
||||||
|
|
||||||
|
"@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.16", "https://registry.npmmirror.com/@aws-sdk/xml-builder/-/xml-builder-3.972.16.tgz", { "dependencies": { "@smithy/types": "^4.13.1", "fast-xml-parser": "5.5.8", "tslib": "^2.6.2" } }, "sha512-iu2pyvaqmeatIJLURLqx9D+4jKAdTH20ntzB6BFwjyN7V960r4jK32mx0Zf7YbtOYAbmbtQfDNuL60ONinyw7A=="],
|
||||||
|
|
||||||
|
"@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.4", "https://registry.npmmirror.com/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", {}, "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ=="],
|
||||||
|
|
||||||
|
"@babel/runtime": ["@babel/runtime@7.29.2", "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.2.tgz", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="],
|
||||||
|
|
||||||
|
"@commander-js/extra-typings": ["@commander-js/extra-typings@14.0.0", "https://registry.npmmirror.com/@commander-js/extra-typings/-/extra-typings-14.0.0.tgz", { "peerDependencies": { "commander": "~14.0.0" } }, "sha512-hIn0ncNaJRLkZrxBIp5AsW/eXEHNKYQBh0aPdoUqNgD+Io3NIykQqpKFyKcuasZhicGaEZJX/JBSIkZ4e5x8Dg=="],
|
||||||
|
|
||||||
|
"@growthbook/growthbook": ["@growthbook/growthbook@1.6.5", "https://registry.npmmirror.com/@growthbook/growthbook/-/growthbook-1.6.5.tgz", { "dependencies": { "dom-mutator": "^0.6.0" } }, "sha512-mUaMsgeUTpRIUOTn33EUXHRK6j7pxBjwqH4WpQyq+pukjd1AIzWlEa6w7i6bInJUcweGgP2beXZmaP6b6UPn7A=="],
|
||||||
|
|
||||||
|
"@hono/node-server": ["@hono/node-server@1.19.12", "https://registry.npmmirror.com/@hono/node-server/-/node-server-1.19.12.tgz", { "peerDependencies": { "hono": "^4" } }, "sha512-txsUW4SQ1iilgE0l9/e9VQWmELXifEFvmdA1j6WFh/aFPj99hIntrSsq/if0UWyGVkmrRPKA1wCeP+UCr1B9Uw=="],
|
||||||
|
|
||||||
|
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "https://registry.npmmirror.com/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/api": ["@opentelemetry/api@1.9.1", "https://registry.npmmirror.com/@opentelemetry/api/-/api-1.9.1.tgz", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="],
|
||||||
|
|
||||||
|
"@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.214.0", "https://registry.npmmirror.com/@opentelemetry/api-logs/-/api-logs-0.214.0.tgz", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-40lSJeqYO8Uz2Yj7u94/SJWE/wONa7rmMKjI1ZcIjgf3MHNHv1OZUCrCETGuaRF62d5pQD1wKIW+L4lmSMTzZA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/core": ["@opentelemetry/core@2.6.1", "https://registry.npmmirror.com/@opentelemetry/core/-/core-2.6.1.tgz", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-8xHSGWpJP9wBxgBpnqGL0R3PbdWQndL1Qp50qrg71+B28zK5OQmUgcDKLJgzyAAV38t4tOyLMGDD60LneR5W8g=="],
|
||||||
|
|
||||||
|
"@opentelemetry/resources": ["@opentelemetry/resources@2.6.1", "https://registry.npmmirror.com/@opentelemetry/resources/-/resources-2.6.1.tgz", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-lID/vxSuKWXM55XhAKNoYXu9Cutoq5hFdkbTdI/zDKQktXzcWBVhNsOkiZFTMU9UtEWuGRNe0HUgmsFldIdxVA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.214.0", "https://registry.npmmirror.com/@opentelemetry/sdk-logs/-/sdk-logs-0.214.0.tgz", { "dependencies": { "@opentelemetry/api-logs": "0.214.0", "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-zf6acnScjhsaBUU22zXZ/sLWim1dfhUAbGXdMmHmNG3LfBnQ3DKsOCITb2IZwoUsNNMTogqFKBnlIPPftUgGwA=="],
|
||||||
|
|
||||||
|
"@opentelemetry/sdk-metrics": ["@opentelemetry/sdk-metrics@2.6.1", "https://registry.npmmirror.com/@opentelemetry/sdk-metrics/-/sdk-metrics-2.6.1.tgz", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <1.10.0" } }, "sha512-9t9hJHX15meBy2NmTJxL+NJfXmnausR2xUDvE19XQce0Qi/GBtDGamU8nS1RMbdgDmhgpm3VaOu2+fiS/SfTpQ=="],
|
||||||
|
|
||||||
|
"@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.6.1", "https://registry.npmmirror.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.6.1.tgz", { "dependencies": { "@opentelemetry/core": "2.6.1", "@opentelemetry/resources": "2.6.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-r86ut4T1e8vNwB35CqCcKd45yzqH6/6Wzvpk2/cZB8PsPLlZFTvrh8yfOS3CYZYcUmAx4hHTZJ8AO8Dj8nrdhw=="],
|
||||||
|
|
||||||
|
"@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "https://registry.npmmirror.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.40.0.tgz", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="],
|
||||||
|
|
||||||
|
"@pondwader/socks5-server": ["@pondwader/socks5-server@1.0.10", "https://registry.npmmirror.com/@pondwader/socks5-server/-/socks5-server-1.0.10.tgz", {}, "sha512-bQY06wzzR8D2+vVCUoBsr5QS2U6UgPUQRmErNwtsuI6vLcyRKkafjkr3KxbtGFf9aBBIV2mcvlsKD1UYaIV+sg=="],
|
||||||
|
|
||||||
|
"@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "https://registry.npmmirror.com/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="],
|
||||||
|
|
||||||
|
"@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "https://registry.npmmirror.com/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="],
|
||||||
|
|
||||||
|
"@smithy/config-resolver": ["@smithy/config-resolver@4.4.13", "https://registry.npmmirror.com/@smithy/config-resolver/-/config-resolver-4.4.13.tgz", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg=="],
|
||||||
|
|
||||||
|
"@smithy/core": ["@smithy/core@3.23.13", "https://registry.npmmirror.com/@smithy/core/-/core-3.23.13.tgz", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.21", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-J+2TT9D6oGsUVXVEMvz8h2EmdVnkBiy2auCie4aSJMvKlzUtO5hqjEzXhoCUkIMo7gAYjbQcN0g/MMSXEhDs1Q=="],
|
||||||
|
|
||||||
|
"@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.12", "https://registry.npmmirror.com/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.12.tgz", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg=="],
|
||||||
|
|
||||||
|
"@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.12", "https://registry.npmmirror.com/@smithy/eventstream-codec/-/eventstream-codec-4.2.12.tgz", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA=="],
|
||||||
|
|
||||||
|
"@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.12", "https://registry.npmmirror.com/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.12.tgz", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-XUSuMxlTxV5pp4VpqZf6Sa3vT/Q75FVkLSpSSE3KkWBvAQWeuWt1msTv8fJfgA4/jcJhrbrbMzN1AC/hvPmm5A=="],
|
||||||
|
|
||||||
|
"@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.12", "https://registry.npmmirror.com/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.12.tgz", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-7epsAZ3QvfHkngz6RXQYseyZYHlmWXSTPOfPmXkiS+zA6TBNo1awUaMFL9vxyXlGdoELmCZyZe1nQE+imbmV+Q=="],
|
||||||
|
|
||||||
|
"@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.12", "https://registry.npmmirror.com/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.12.tgz", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-D1pFuExo31854eAvg89KMn9Oab/wEeJR6Buy32B49A9Ogdtx5fwZPqBHUlDzaCDpycTFk2+fSQgX689Qsk7UGA=="],
|
||||||
|
|
||||||
|
"@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.12", "https://registry.npmmirror.com/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.12.tgz", { "dependencies": { "@smithy/eventstream-codec": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ=="],
|
||||||
|
|
||||||
|
"@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.15", "https://registry.npmmirror.com/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.15.tgz", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A=="],
|
||||||
|
|
||||||
|
"@smithy/hash-node": ["@smithy/hash-node@4.2.12", "https://registry.npmmirror.com/@smithy/hash-node/-/hash-node-4.2.12.tgz", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w=="],
|
||||||
|
|
||||||
|
"@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.12", "https://registry.npmmirror.com/@smithy/invalid-dependency/-/invalid-dependency-4.2.12.tgz", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g=="],
|
||||||
|
|
||||||
|
"@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "https://registry.npmmirror.com/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="],
|
||||||
|
|
||||||
|
"@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.12", "https://registry.npmmirror.com/@smithy/middleware-content-length/-/middleware-content-length-4.2.12.tgz", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA=="],
|
||||||
|
|
||||||
|
"@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.28", "https://registry.npmmirror.com/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.28.tgz", { "dependencies": { "@smithy/core": "^3.23.13", "@smithy/middleware-serde": "^4.2.16", "@smithy/node-config-provider": "^4.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-p1gfYpi91CHcs5cBq982UlGlDrxoYUX6XdHSo91cQ2KFuz6QloHosO7Jc60pJiVmkWrKOV8kFYlGFFbQ2WUKKQ=="],
|
||||||
|
|
||||||
|
"@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.46", "https://registry.npmmirror.com/@smithy/middleware-retry/-/middleware-retry-4.4.46.tgz", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/service-error-classification": "^4.2.12", "@smithy/smithy-client": "^4.12.8", "@smithy/types": "^4.13.1", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.13", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-SpvWNNOPOrKQGUqZbEPO+es+FRXMWvIyzUKUOYdDgdlA6BdZj/R58p4umoQ76c2oJC44PiM7mKizyyex1IJzow=="],
|
||||||
|
|
||||||
|
"@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.16", "https://registry.npmmirror.com/@smithy/middleware-serde/-/middleware-serde-4.2.16.tgz", { "dependencies": { "@smithy/core": "^3.23.13", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-beqfV+RZ9RSv+sQqor3xroUUYgRFCGRw6niGstPG8zO9LgTl0B0MCucxjmrH/2WwksQN7UUgI7KNANoZv+KALA=="],
|
||||||
|
|
||||||
|
"@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.12", "https://registry.npmmirror.com/@smithy/middleware-stack/-/middleware-stack-4.2.12.tgz", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw=="],
|
||||||
|
|
||||||
|
"@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.12", "https://registry.npmmirror.com/@smithy/node-config-provider/-/node-config-provider-4.3.12.tgz", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw=="],
|
||||||
|
|
||||||
|
"@smithy/node-http-handler": ["@smithy/node-http-handler@4.5.1", "https://registry.npmmirror.com/@smithy/node-http-handler/-/node-http-handler-4.5.1.tgz", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ejjxdAXjkPIs9lyYyVutOGNOraqUE9v/NjGMKwwFrfOM354wfSD8lmlj8hVwUzQmlLLF4+udhfCX9Exnbmvfzw=="],
|
||||||
|
|
||||||
|
"@smithy/property-provider": ["@smithy/property-provider@4.2.12", "https://registry.npmmirror.com/@smithy/property-provider/-/property-provider-4.2.12.tgz", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="],
|
||||||
|
|
||||||
|
"@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "https://registry.npmmirror.com/@smithy/protocol-http/-/protocol-http-5.3.12.tgz", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="],
|
||||||
|
|
||||||
|
"@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.12", "https://registry.npmmirror.com/@smithy/querystring-builder/-/querystring-builder-4.2.12.tgz", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg=="],
|
||||||
|
|
||||||
|
"@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.12", "https://registry.npmmirror.com/@smithy/querystring-parser/-/querystring-parser-4.2.12.tgz", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw=="],
|
||||||
|
|
||||||
|
"@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.12", "https://registry.npmmirror.com/@smithy/service-error-classification/-/service-error-classification-4.2.12.tgz", { "dependencies": { "@smithy/types": "^4.13.1" } }, "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ=="],
|
||||||
|
|
||||||
|
"@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.7", "https://registry.npmmirror.com/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.7.tgz", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw=="],
|
||||||
|
|
||||||
|
"@smithy/signature-v4": ["@smithy/signature-v4@5.3.12", "https://registry.npmmirror.com/@smithy/signature-v4/-/signature-v4-5.3.12.tgz", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw=="],
|
||||||
|
|
||||||
|
"@smithy/smithy-client": ["@smithy/smithy-client@4.12.8", "https://registry.npmmirror.com/@smithy/smithy-client/-/smithy-client-4.12.8.tgz", { "dependencies": { "@smithy/core": "^3.23.13", "@smithy/middleware-endpoint": "^4.4.28", "@smithy/middleware-stack": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.21", "tslib": "^2.6.2" } }, "sha512-aJaAX7vHe5i66smoSSID7t4rKY08PbD8EBU7DOloixvhOozfYWdcSYE4l6/tjkZ0vBZhGjheWzB2mh31sLgCMA=="],
|
||||||
|
|
||||||
|
"@smithy/types": ["@smithy/types@4.13.1", "https://registry.npmmirror.com/@smithy/types/-/types-4.13.1.tgz", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="],
|
||||||
|
|
||||||
|
"@smithy/url-parser": ["@smithy/url-parser@4.2.12", "https://registry.npmmirror.com/@smithy/url-parser/-/url-parser-4.2.12.tgz", { "dependencies": { "@smithy/querystring-parser": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA=="],
|
||||||
|
|
||||||
|
"@smithy/util-base64": ["@smithy/util-base64@4.3.2", "https://registry.npmmirror.com/@smithy/util-base64/-/util-base64-4.3.2.tgz", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ=="],
|
||||||
|
|
||||||
|
"@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.2", "https://registry.npmmirror.com/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ=="],
|
||||||
|
|
||||||
|
"@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.3", "https://registry.npmmirror.com/@smithy/util-body-length-node/-/util-body-length-node-4.2.3.tgz", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g=="],
|
||||||
|
|
||||||
|
"@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "https://registry.npmmirror.com/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="],
|
||||||
|
|
||||||
|
"@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.2", "https://registry.npmmirror.com/@smithy/util-config-provider/-/util-config-provider-4.2.2.tgz", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ=="],
|
||||||
|
|
||||||
|
"@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.44", "https://registry.npmmirror.com/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.44.tgz", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.8", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-eZg6XzaCbVr2S5cAErU5eGBDaOVTuTo1I65i4tQcHENRcZ8rMWhQy1DaIYUSLyZjsfXvmCqZrstSMYyGFocvHA=="],
|
||||||
|
|
||||||
|
"@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.48", "https://registry.npmmirror.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.48.tgz", { "dependencies": { "@smithy/config-resolver": "^4.4.13", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.8", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-FqOKTlqSaoV3nzO55pMs5NBnZX8EhoI0DGmn9kbYeXWppgHD6dchyuj2HLqp4INJDJbSrj6OFYJkAh/WhSzZPg=="],
|
||||||
|
|
||||||
|
"@smithy/util-endpoints": ["@smithy/util-endpoints@3.3.3", "https://registry.npmmirror.com/@smithy/util-endpoints/-/util-endpoints-3.3.3.tgz", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig=="],
|
||||||
|
|
||||||
|
"@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.2", "https://registry.npmmirror.com/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg=="],
|
||||||
|
|
||||||
|
"@smithy/util-middleware": ["@smithy/util-middleware@4.2.12", "https://registry.npmmirror.com/@smithy/util-middleware/-/util-middleware-4.2.12.tgz", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ=="],
|
||||||
|
|
||||||
|
"@smithy/util-retry": ["@smithy/util-retry@4.2.13", "https://registry.npmmirror.com/@smithy/util-retry/-/util-retry-4.2.13.tgz", { "dependencies": { "@smithy/service-error-classification": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-qQQsIvL0MGIbUjeSrg0/VlQ3jGNKyM3/2iU3FPNgy01z+Sp4OvcaxbgIoFOTvB61ZoohtutuOvOcgmhbD0katQ=="],
|
||||||
|
|
||||||
|
"@smithy/util-stream": ["@smithy/util-stream@4.5.21", "https://registry.npmmirror.com/@smithy/util-stream/-/util-stream-4.5.21.tgz", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.1", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-KzSg+7KKywLnkoKejRtIBXDmwBfjGvg1U1i/etkC7XSWUyFCoLno1IohV2c74IzQqdhX5y3uE44r/8/wuK+A7Q=="],
|
||||||
|
|
||||||
|
"@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.2", "https://registry.npmmirror.com/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw=="],
|
||||||
|
|
||||||
|
"@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "https://registry.npmmirror.com/@smithy/util-utf8/-/util-utf8-4.2.2.tgz", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="],
|
||||||
|
|
||||||
|
"@smithy/uuid": ["@smithy/uuid@1.1.2", "https://registry.npmmirror.com/@smithy/uuid/-/uuid-1.1.2.tgz", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g=="],
|
||||||
|
|
||||||
|
"@types/lodash": ["@types/lodash@4.17.24", "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.24.tgz", {}, "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ=="],
|
||||||
|
|
||||||
|
"@types/lodash-es": ["@types/lodash-es@4.17.12", "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz", { "dependencies": { "@types/lodash": "*" } }, "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ=="],
|
||||||
|
|
||||||
|
"accepts": ["accepts@2.0.0", "https://registry.npmmirror.com/accepts/-/accepts-2.0.0.tgz", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
|
||||||
|
|
||||||
|
"agent-base": ["agent-base@8.0.0", "https://registry.npmmirror.com/agent-base/-/agent-base-8.0.0.tgz", {}, "sha512-QT8i0hCz6C/KQ+KTAbSNwCHDGdmUJl2tp2ZpNlGSWCfhUNVbYG2WLE3MdZGBAgXPV4GAvjGMxo+C1hroyxmZEg=="],
|
||||||
|
|
||||||
|
"ajv": ["ajv@8.18.0", "https://registry.npmmirror.com/ajv/-/ajv-8.18.0.tgz", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="],
|
||||||
|
|
||||||
|
"ajv-formats": ["ajv-formats@3.0.1", "https://registry.npmmirror.com/ajv-formats/-/ajv-formats-3.0.1.tgz", { "dependencies": { "ajv": "^8.0.0" }, "peerDependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
|
||||||
|
|
||||||
|
"ansi-escapes": ["ansi-escapes@7.3.0", "https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-7.3.0.tgz", { "dependencies": { "environment": "^1.0.0" } }, "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg=="],
|
||||||
|
|
||||||
|
"ansi-regex": ["ansi-regex@6.2.2", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.2.2.tgz", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
|
||||||
|
|
||||||
|
"ansi-styles": ["ansi-styles@6.2.3", "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||||
|
|
||||||
|
"asciichart": ["asciichart@1.5.25", "https://registry.npmmirror.com/asciichart/-/asciichart-1.5.25.tgz", {}, "sha512-PNxzXIPPOtWq8T7bgzBtk9cI2lgS4SJZthUHEiQ1aoIc3lNzGfUvIvo9LiAnq26TACo9t1/4qP6KTGAUbzX9Xg=="],
|
||||||
|
|
||||||
|
"asynckit": ["asynckit@0.4.0", "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
||||||
|
|
||||||
|
"auto-bind": ["auto-bind@5.0.1", "https://registry.npmmirror.com/auto-bind/-/auto-bind-5.0.1.tgz", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="],
|
||||||
|
|
||||||
|
"axios": ["axios@1.14.0", "https://registry.npmmirror.com/axios/-/axios-1.14.0.tgz", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } }, "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ=="],
|
||||||
|
|
||||||
|
"base64-js": ["base64-js@1.5.1", "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
|
||||||
|
|
||||||
|
"bidi-js": ["bidi-js@1.0.3", "https://registry.npmmirror.com/bidi-js/-/bidi-js-1.0.3.tgz", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="],
|
||||||
|
|
||||||
|
"bignumber.js": ["bignumber.js@9.3.1", "https://registry.npmmirror.com/bignumber.js/-/bignumber.js-9.3.1.tgz", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
|
||||||
|
|
||||||
|
"body-parser": ["body-parser@2.2.2", "https://registry.npmmirror.com/body-parser/-/body-parser-2.2.2.tgz", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="],
|
||||||
|
|
||||||
|
"bowser": ["bowser@2.14.1", "https://registry.npmmirror.com/bowser/-/bowser-2.14.1.tgz", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="],
|
||||||
|
|
||||||
|
"buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "https://registry.npmmirror.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
|
||||||
|
|
||||||
|
"bytes": ["bytes@3.1.2", "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
|
||||||
|
|
||||||
|
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
|
||||||
|
|
||||||
|
"call-bound": ["call-bound@1.0.4", "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
|
||||||
|
|
||||||
|
"camelcase": ["camelcase@5.3.1", "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="],
|
||||||
|
|
||||||
|
"chalk": ["chalk@5.6.2", "https://registry.npmmirror.com/chalk/-/chalk-5.6.2.tgz", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||||
|
|
||||||
|
"chokidar": ["chokidar@5.0.0", "https://registry.npmmirror.com/chokidar/-/chokidar-5.0.0.tgz", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
|
||||||
|
|
||||||
|
"cli-boxes": ["cli-boxes@4.0.1", "https://registry.npmmirror.com/cli-boxes/-/cli-boxes-4.0.1.tgz", {}, "sha512-5IOn+jcCEHEraYolBPs/sT4BxYCe2nHg374OPiItB1O96KZFseS2gthU4twyYzeDcFew4DaUM/xwc5BQf08JJw=="],
|
||||||
|
|
||||||
|
"cli-cursor": ["cli-cursor@4.0.0", "https://registry.npmmirror.com/cli-cursor/-/cli-cursor-4.0.0.tgz", { "dependencies": { "restore-cursor": "^4.0.0" } }, "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg=="],
|
||||||
|
|
||||||
|
"cli-truncate": ["cli-truncate@5.2.0", "https://registry.npmmirror.com/cli-truncate/-/cli-truncate-5.2.0.tgz", { "dependencies": { "slice-ansi": "^8.0.0", "string-width": "^8.2.0" } }, "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw=="],
|
||||||
|
|
||||||
|
"cliui": ["cliui@6.0.0", "https://registry.npmmirror.com/cliui/-/cliui-6.0.0.tgz", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="],
|
||||||
|
|
||||||
|
"code-excerpt": ["code-excerpt@4.0.0", "https://registry.npmmirror.com/code-excerpt/-/code-excerpt-4.0.0.tgz", { "dependencies": { "convert-to-spaces": "^2.0.1" } }, "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA=="],
|
||||||
|
|
||||||
|
"color-convert": ["color-convert@2.0.1", "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||||
|
|
||||||
|
"color-name": ["color-name@1.1.4", "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||||
|
|
||||||
|
"combined-stream": ["combined-stream@1.0.8", "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
|
||||||
|
|
||||||
|
"commander": ["commander@12.1.0", "https://registry.npmmirror.com/commander/-/commander-12.1.0.tgz", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
|
||||||
|
|
||||||
|
"content-disposition": ["content-disposition@1.0.1", "https://registry.npmmirror.com/content-disposition/-/content-disposition-1.0.1.tgz", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="],
|
||||||
|
|
||||||
|
"content-type": ["content-type@1.0.5", "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
|
||||||
|
|
||||||
|
"convert-to-spaces": ["convert-to-spaces@2.0.1", "https://registry.npmmirror.com/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", {}, "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ=="],
|
||||||
|
|
||||||
|
"cookie": ["cookie@0.7.2", "https://registry.npmmirror.com/cookie/-/cookie-0.7.2.tgz", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
|
||||||
|
|
||||||
|
"cookie-signature": ["cookie-signature@1.2.2", "https://registry.npmmirror.com/cookie-signature/-/cookie-signature-1.2.2.tgz", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
|
||||||
|
|
||||||
|
"cors": ["cors@2.8.6", "https://registry.npmmirror.com/cors/-/cors-2.8.6.tgz", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="],
|
||||||
|
|
||||||
|
"cross-spawn": ["cross-spawn@7.0.6", "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
||||||
|
|
||||||
|
"cssfilter": ["cssfilter@0.0.10", "https://registry.npmmirror.com/cssfilter/-/cssfilter-0.0.10.tgz", {}, "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw=="],
|
||||||
|
|
||||||
|
"data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "https://registry.npmmirror.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
|
||||||
|
|
||||||
|
"debug": ["debug@4.4.3", "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
|
||||||
|
|
||||||
|
"decamelize": ["decamelize@1.2.0", "https://registry.npmmirror.com/decamelize/-/decamelize-1.2.0.tgz", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="],
|
||||||
|
|
||||||
|
"delayed-stream": ["delayed-stream@1.0.0", "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
|
||||||
|
|
||||||
|
"depd": ["depd@2.0.0", "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
|
||||||
|
|
||||||
|
"diff": ["diff@8.0.4", "https://registry.npmmirror.com/diff/-/diff-8.0.4.tgz", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="],
|
||||||
|
|
||||||
|
"dijkstrajs": ["dijkstrajs@1.0.3", "https://registry.npmmirror.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz", {}, "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="],
|
||||||
|
|
||||||
|
"dom-mutator": ["dom-mutator@0.6.0", "https://registry.npmmirror.com/dom-mutator/-/dom-mutator-0.6.0.tgz", {}, "sha512-iCt9o0aYfXMUkz/43ZOAUFQYotjGB+GNbYJiJdz4TgXkyToXbbRy5S6FbTp72lRBtfpUMwEc1KmpFEU4CZeoNg=="],
|
||||||
|
|
||||||
|
"dunder-proto": ["dunder-proto@1.0.1", "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||||
|
|
||||||
|
"ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "https://registry.npmmirror.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
|
||||||
|
|
||||||
|
"ee-first": ["ee-first@1.1.1", "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
|
||||||
|
|
||||||
|
"emoji-regex": ["emoji-regex@10.6.0", "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-10.6.0.tgz", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||||
|
|
||||||
|
"encodeurl": ["encodeurl@2.0.0", "https://registry.npmmirror.com/encodeurl/-/encodeurl-2.0.0.tgz", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
|
||||||
|
|
||||||
|
"env-paths": ["env-paths@4.0.0", "https://registry.npmmirror.com/env-paths/-/env-paths-4.0.0.tgz", { "dependencies": { "is-safe-filename": "^0.1.0" } }, "sha512-pxP8eL2SwwaTRi/KHYwLYXinDs7gL3jxFcBYmEdYfZmZXbaVDvdppd0XBU8qVz03rDfKZMXg1omHCbsJjZrMsw=="],
|
||||||
|
|
||||||
|
"environment": ["environment@1.1.0", "https://registry.npmmirror.com/environment/-/environment-1.1.0.tgz", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="],
|
||||||
|
|
||||||
|
"es-define-property": ["es-define-property@1.0.1", "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
|
||||||
|
|
||||||
|
"es-errors": ["es-errors@1.3.0", "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
|
||||||
|
|
||||||
|
"es-object-atoms": ["es-object-atoms@1.1.1", "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
|
||||||
|
|
||||||
|
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
|
||||||
|
|
||||||
|
"es-toolkit": ["es-toolkit@1.45.1", "https://registry.npmmirror.com/es-toolkit/-/es-toolkit-1.45.1.tgz", {}, "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw=="],
|
||||||
|
|
||||||
|
"escape-html": ["escape-html@1.0.3", "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
|
||||||
|
|
||||||
|
"escape-string-regexp": ["escape-string-regexp@2.0.0", "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
|
||||||
|
|
||||||
|
"etag": ["etag@1.8.1", "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
|
||||||
|
|
||||||
|
"eventsource": ["eventsource@3.0.7", "https://registry.npmmirror.com/eventsource/-/eventsource-3.0.7.tgz", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
|
||||||
|
|
||||||
|
"eventsource-parser": ["eventsource-parser@3.0.6", "https://registry.npmmirror.com/eventsource-parser/-/eventsource-parser-3.0.6.tgz", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="],
|
||||||
|
|
||||||
|
"execa": ["execa@9.6.1", "https://registry.npmmirror.com/execa/-/execa-9.6.1.tgz", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="],
|
||||||
|
|
||||||
|
"express": ["express@5.2.1", "https://registry.npmmirror.com/express/-/express-5.2.1.tgz", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
|
||||||
|
|
||||||
|
"express-rate-limit": ["express-rate-limit@8.3.2", "https://registry.npmmirror.com/express-rate-limit/-/express-rate-limit-8.3.2.tgz", { "dependencies": { "ip-address": "10.1.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg=="],
|
||||||
|
|
||||||
|
"extend": ["extend@3.0.2", "https://registry.npmmirror.com/extend/-/extend-3.0.2.tgz", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
|
||||||
|
|
||||||
|
"fast-deep-equal": ["fast-deep-equal@3.1.3", "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||||
|
|
||||||
|
"fast-uri": ["fast-uri@3.1.0", "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.1.0.tgz", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
|
||||||
|
|
||||||
|
"fast-xml-builder": ["fast-xml-builder@1.1.4", "https://registry.npmmirror.com/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg=="],
|
||||||
|
|
||||||
|
"fast-xml-parser": ["fast-xml-parser@5.5.8", "https://registry.npmmirror.com/fast-xml-parser/-/fast-xml-parser-5.5.8.tgz", { "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.2.0", "strnum": "^2.2.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-Z7Fh2nVQSb2d+poDViM063ix2ZGt9jmY1nWhPfHBOK2Hgnb/OW3P4Et3P/81SEej0J7QbWtJqxO05h8QYfK7LQ=="],
|
||||||
|
|
||||||
|
"fetch-blob": ["fetch-blob@3.2.0", "https://registry.npmmirror.com/fetch-blob/-/fetch-blob-3.2.0.tgz", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
|
||||||
|
|
||||||
|
"figures": ["figures@6.1.0", "https://registry.npmmirror.com/figures/-/figures-6.1.0.tgz", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="],
|
||||||
|
|
||||||
|
"finalhandler": ["finalhandler@2.1.1", "https://registry.npmmirror.com/finalhandler/-/finalhandler-2.1.1.tgz", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
|
||||||
|
|
||||||
|
"find-up": ["find-up@4.1.0", "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
|
||||||
|
|
||||||
|
"follow-redirects": ["follow-redirects@1.15.11", "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
|
||||||
|
|
||||||
|
"form-data": ["form-data@4.0.5", "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
|
||||||
|
|
||||||
|
"formdata-polyfill": ["formdata-polyfill@4.0.10", "https://registry.npmmirror.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
|
||||||
|
|
||||||
|
"forwarded": ["forwarded@0.2.0", "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
|
||||||
|
|
||||||
|
"fresh": ["fresh@2.0.0", "https://registry.npmmirror.com/fresh/-/fresh-2.0.0.tgz", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
|
||||||
|
|
||||||
|
"function-bind": ["function-bind@1.1.2", "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||||
|
|
||||||
|
"fuse.js": ["fuse.js@7.1.0", "https://registry.npmmirror.com/fuse.js/-/fuse.js-7.1.0.tgz", {}, "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ=="],
|
||||||
|
|
||||||
|
"gaxios": ["gaxios@7.1.4", "https://registry.npmmirror.com/gaxios/-/gaxios-7.1.4.tgz", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-bTIgTsM2bWn3XklZISBTQX7ZSddGW+IO3bMdGaemHZ3tbqExMENHLx6kKZ/KlejgrMtj8q7wBItt51yegqalrA=="],
|
||||||
|
|
||||||
|
"gcp-metadata": ["gcp-metadata@8.1.2", "https://registry.npmmirror.com/gcp-metadata/-/gcp-metadata-8.1.2.tgz", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="],
|
||||||
|
|
||||||
|
"get-caller-file": ["get-caller-file@2.0.5", "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
||||||
|
|
||||||
|
"get-east-asian-width": ["get-east-asian-width@1.5.0", "https://registry.npmmirror.com/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", {}, "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA=="],
|
||||||
|
|
||||||
|
"get-intrinsic": ["get-intrinsic@1.3.0", "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
|
||||||
|
|
||||||
|
"get-proto": ["get-proto@1.0.1", "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||||
|
|
||||||
|
"get-stream": ["get-stream@9.0.1", "https://registry.npmmirror.com/get-stream/-/get-stream-9.0.1.tgz", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="],
|
||||||
|
|
||||||
|
"google-auth-library": ["google-auth-library@10.6.2", "https://registry.npmmirror.com/google-auth-library/-/google-auth-library-10.6.2.tgz", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.1.4", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" } }, "sha512-e27Z6EThmVNNvtYASwQxose/G57rkRuaRbQyxM2bvYLLX/GqWZ5chWq2EBoUchJbCc57eC9ArzO5wMsEmWftCw=="],
|
||||||
|
|
||||||
|
"google-logging-utils": ["google-logging-utils@1.1.3", "https://registry.npmmirror.com/google-logging-utils/-/google-logging-utils-1.1.3.tgz", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="],
|
||||||
|
|
||||||
|
"gopd": ["gopd@1.2.0", "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
|
||||||
|
|
||||||
|
"graceful-fs": ["graceful-fs@4.2.11", "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||||
|
|
||||||
|
"has-flag": ["has-flag@5.0.1", "https://registry.npmmirror.com/has-flag/-/has-flag-5.0.1.tgz", {}, "sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA=="],
|
||||||
|
|
||||||
|
"has-symbols": ["has-symbols@1.1.0", "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
|
||||||
|
|
||||||
|
"has-tostringtag": ["has-tostringtag@1.0.2", "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
|
||||||
|
|
||||||
|
"hasown": ["hasown@2.0.2", "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||||
|
|
||||||
|
"highlight.js": ["highlight.js@11.11.1", "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.11.1.tgz", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="],
|
||||||
|
|
||||||
|
"hono": ["hono@4.12.9", "https://registry.npmmirror.com/hono/-/hono-4.12.9.tgz", {}, "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA=="],
|
||||||
|
|
||||||
|
"http-errors": ["http-errors@2.0.1", "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.1.tgz", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
|
||||||
|
|
||||||
|
"https-proxy-agent": ["https-proxy-agent@8.0.0", "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-8.0.0.tgz", { "dependencies": { "agent-base": "8.0.0", "debug": "^4.3.4" } }, "sha512-YYeW+iCnAS3xhvj2dvVoWgsbca3RfQy/IlaNHHOtDmU0jMqPI9euIq3Y9BJETdxk16h9NHHCKqp/KB9nIMStCQ=="],
|
||||||
|
|
||||||
|
"human-signals": ["human-signals@8.0.1", "https://registry.npmmirror.com/human-signals/-/human-signals-8.0.1.tgz", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="],
|
||||||
|
|
||||||
|
"iconv-lite": ["iconv-lite@0.7.2", "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.2.tgz", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
|
||||||
|
|
||||||
|
"ignore": ["ignore@7.0.5", "https://registry.npmmirror.com/ignore/-/ignore-7.0.5.tgz", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||||
|
|
||||||
|
"indent-string": ["indent-string@5.0.0", "https://registry.npmmirror.com/indent-string/-/indent-string-5.0.0.tgz", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="],
|
||||||
|
|
||||||
|
"inherits": ["inherits@2.0.4", "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
||||||
|
|
||||||
|
"ink": ["ink@6.8.0", "https://registry.npmmirror.com/ink/-/ink-6.8.0.tgz", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.4", "ansi-escapes": "^7.3.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.6.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^5.1.1", "code-excerpt": "^4.0.0", "es-toolkit": "^1.39.10", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.33.0", "scheduler": "^0.27.0", "signal-exit": "^3.0.7", "slice-ansi": "^8.0.0", "stack-utils": "^2.0.6", "string-width": "^8.1.1", "terminal-size": "^4.0.1", "type-fest": "^5.4.1", "widest-line": "^6.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": ">=6.1.2" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA=="],
|
||||||
|
|
||||||
|
"ip-address": ["ip-address@10.1.0", "https://registry.npmmirror.com/ip-address/-/ip-address-10.1.0.tgz", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="],
|
||||||
|
|
||||||
|
"ipaddr.js": ["ipaddr.js@1.9.1", "https://registry.npmmirror.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
|
||||||
|
|
||||||
|
"is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="],
|
||||||
|
|
||||||
|
"is-in-ci": ["is-in-ci@2.0.0", "https://registry.npmmirror.com/is-in-ci/-/is-in-ci-2.0.0.tgz", { "bin": "cli.js" }, "sha512-cFeerHriAnhrQSbpAxL37W1wcJKUUX07HyLWZCW1URJT/ra3GyUTzBgUnh24TMVfNTV2Hij2HLxkPHFZfOZy5w=="],
|
||||||
|
|
||||||
|
"is-plain-obj": ["is-plain-obj@4.1.0", "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
|
||||||
|
|
||||||
|
"is-promise": ["is-promise@4.0.0", "https://registry.npmmirror.com/is-promise/-/is-promise-4.0.0.tgz", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
|
||||||
|
|
||||||
|
"is-safe-filename": ["is-safe-filename@0.1.1", "https://registry.npmmirror.com/is-safe-filename/-/is-safe-filename-0.1.1.tgz", {}, "sha512-4SrR7AdnY11LHfDKTZY1u6Ga3RuxZdl3YKWWShO5iyuG5h8QS4GD2tOb04peBJ5I7pXbR+CGBNEhTcwK+FzN3g=="],
|
||||||
|
|
||||||
|
"is-stream": ["is-stream@4.0.1", "https://registry.npmmirror.com/is-stream/-/is-stream-4.0.1.tgz", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="],
|
||||||
|
|
||||||
|
"is-unicode-supported": ["is-unicode-supported@2.1.0", "https://registry.npmmirror.com/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
|
||||||
|
|
||||||
|
"isexe": ["isexe@2.0.0", "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||||
|
|
||||||
|
"jose": ["jose@6.2.2", "https://registry.npmmirror.com/jose/-/jose-6.2.2.tgz", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="],
|
||||||
|
|
||||||
|
"json-bigint": ["json-bigint@1.0.0", "https://registry.npmmirror.com/json-bigint/-/json-bigint-1.0.0.tgz", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
|
||||||
|
|
||||||
|
"json-schema-to-ts": ["json-schema-to-ts@3.1.1", "https://registry.npmmirror.com/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", { "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" } }, "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g=="],
|
||||||
|
|
||||||
|
"json-schema-traverse": ["json-schema-traverse@1.0.0", "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||||
|
|
||||||
|
"json-schema-typed": ["json-schema-typed@8.0.2", "https://registry.npmmirror.com/json-schema-typed/-/json-schema-typed-8.0.2.tgz", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="],
|
||||||
|
|
||||||
|
"jsonc-parser": ["jsonc-parser@3.3.1", "https://registry.npmmirror.com/jsonc-parser/-/jsonc-parser-3.3.1.tgz", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="],
|
||||||
|
|
||||||
|
"jwa": ["jwa@2.0.1", "https://registry.npmmirror.com/jwa/-/jwa-2.0.1.tgz", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
|
||||||
|
|
||||||
|
"jws": ["jws@4.0.1", "https://registry.npmmirror.com/jws/-/jws-4.0.1.tgz", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
|
||||||
|
|
||||||
|
"locate-path": ["locate-path@5.0.0", "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
|
||||||
|
|
||||||
|
"lodash-es": ["lodash-es@4.17.23", "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.23.tgz", {}, "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg=="],
|
||||||
|
|
||||||
|
"lodash.debounce": ["lodash.debounce@4.0.8", "https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="],
|
||||||
|
|
||||||
|
"lru-cache": ["lru-cache@11.2.7", "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.2.7.tgz", {}, "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA=="],
|
||||||
|
|
||||||
|
"marked": ["marked@17.0.5", "https://registry.npmmirror.com/marked/-/marked-17.0.5.tgz", { "bin": "bin/marked.js" }, "sha512-6hLvc0/JEbRjRgzI6wnT2P1XuM1/RrrDEX0kPt0N7jGm1133g6X7DlxFasUIx+72aKAr904GTxhSLDrd5DIlZg=="],
|
||||||
|
|
||||||
|
"math-intrinsics": ["math-intrinsics@1.1.0", "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||||
|
|
||||||
|
"media-typer": ["media-typer@1.1.0", "https://registry.npmmirror.com/media-typer/-/media-typer-1.1.0.tgz", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
|
||||||
|
|
||||||
|
"merge-descriptors": ["merge-descriptors@2.0.0", "https://registry.npmmirror.com/merge-descriptors/-/merge-descriptors-2.0.0.tgz", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
|
||||||
|
|
||||||
|
"mime-db": ["mime-db@1.54.0", "https://registry.npmmirror.com/mime-db/-/mime-db-1.54.0.tgz", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
|
||||||
|
|
||||||
|
"mime-types": ["mime-types@3.0.2", "https://registry.npmmirror.com/mime-types/-/mime-types-3.0.2.tgz", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
|
||||||
|
|
||||||
|
"mimic-fn": ["mimic-fn@2.1.0", "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
|
||||||
|
|
||||||
|
"ms": ["ms@2.1.3", "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||||
|
|
||||||
|
"negotiator": ["negotiator@1.0.0", "https://registry.npmmirror.com/negotiator/-/negotiator-1.0.0.tgz", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
|
||||||
|
|
||||||
|
"node-domexception": ["node-domexception@1.0.0", "https://registry.npmmirror.com/node-domexception/-/node-domexception-1.0.0.tgz", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
|
||||||
|
|
||||||
|
"node-fetch": ["node-fetch@3.3.2", "https://registry.npmmirror.com/node-fetch/-/node-fetch-3.3.2.tgz", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
|
||||||
|
|
||||||
|
"npm-run-path": ["npm-run-path@6.0.0", "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-6.0.0.tgz", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="],
|
||||||
|
|
||||||
|
"object-assign": ["object-assign@4.1.1", "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
|
||||||
|
|
||||||
|
"object-inspect": ["object-inspect@1.13.4", "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
|
||||||
|
|
||||||
|
"on-finished": ["on-finished@2.4.1", "https://registry.npmmirror.com/on-finished/-/on-finished-2.4.1.tgz", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
|
||||||
|
|
||||||
|
"once": ["once@1.4.0", "https://registry.npmmirror.com/once/-/once-1.4.0.tgz", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
|
||||||
|
|
||||||
|
"onetime": ["onetime@5.1.2", "https://registry.npmmirror.com/onetime/-/onetime-5.1.2.tgz", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
|
||||||
|
|
||||||
|
"p-limit": ["p-limit@2.3.0", "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||||
|
|
||||||
|
"p-locate": ["p-locate@4.1.0", "https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
||||||
|
|
||||||
|
"p-map": ["p-map@7.0.4", "https://registry.npmmirror.com/p-map/-/p-map-7.0.4.tgz", {}, "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ=="],
|
||||||
|
|
||||||
|
"p-try": ["p-try@2.2.0", "https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="],
|
||||||
|
|
||||||
|
"parse-ms": ["parse-ms@4.0.0", "https://registry.npmmirror.com/parse-ms/-/parse-ms-4.0.0.tgz", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="],
|
||||||
|
|
||||||
|
"parseurl": ["parseurl@1.3.3", "https://registry.npmmirror.com/parseurl/-/parseurl-1.3.3.tgz", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
|
||||||
|
|
||||||
|
"patch-console": ["patch-console@2.0.0", "https://registry.npmmirror.com/patch-console/-/patch-console-2.0.0.tgz", {}, "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA=="],
|
||||||
|
|
||||||
|
"path-exists": ["path-exists@4.0.0", "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||||
|
|
||||||
|
"path-expression-matcher": ["path-expression-matcher@1.2.0", "https://registry.npmmirror.com/path-expression-matcher/-/path-expression-matcher-1.2.0.tgz", {}, "sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ=="],
|
||||||
|
|
||||||
|
"path-key": ["path-key@3.1.1", "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||||
|
|
||||||
|
"path-to-regexp": ["path-to-regexp@8.4.1", "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-8.4.1.tgz", {}, "sha512-fvU78fIjZ+SBM9YwCknCvKOUKkLVqtWDVctl0s7xIqfmfb38t2TT4ZU2gHm+Z8xGwgW+QWEU3oQSAzIbo89Ggw=="],
|
||||||
|
|
||||||
|
"picomatch": ["picomatch@4.0.4", "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
|
||||||
|
|
||||||
|
"pkce-challenge": ["pkce-challenge@5.0.1", "https://registry.npmmirror.com/pkce-challenge/-/pkce-challenge-5.0.1.tgz", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="],
|
||||||
|
|
||||||
|
"pngjs": ["pngjs@5.0.0", "https://registry.npmmirror.com/pngjs/-/pngjs-5.0.0.tgz", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="],
|
||||||
|
|
||||||
|
"pretty-ms": ["pretty-ms@9.3.0", "https://registry.npmmirror.com/pretty-ms/-/pretty-ms-9.3.0.tgz", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="],
|
||||||
|
|
||||||
|
"proper-lockfile": ["proper-lockfile@4.1.2", "https://registry.npmmirror.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="],
|
||||||
|
|
||||||
|
"proxy-addr": ["proxy-addr@2.0.7", "https://registry.npmmirror.com/proxy-addr/-/proxy-addr-2.0.7.tgz", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
|
||||||
|
|
||||||
|
"proxy-from-env": ["proxy-from-env@2.1.0", "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-2.1.0.tgz", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="],
|
||||||
|
|
||||||
|
"qrcode": ["qrcode@1.5.4", "https://registry.npmmirror.com/qrcode/-/qrcode-1.5.4.tgz", { "dependencies": { "dijkstrajs": "^1.0.1", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, "bin": "bin/qrcode" }, "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg=="],
|
||||||
|
|
||||||
|
"qs": ["qs@6.15.0", "https://registry.npmmirror.com/qs/-/qs-6.15.0.tgz", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ=="],
|
||||||
|
|
||||||
|
"range-parser": ["range-parser@1.2.1", "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
|
||||||
|
|
||||||
|
"raw-body": ["raw-body@3.0.2", "https://registry.npmmirror.com/raw-body/-/raw-body-3.0.2.tgz", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
|
||||||
|
|
||||||
|
"react": ["react@19.2.4", "https://registry.npmmirror.com/react/-/react-19.2.4.tgz", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
|
||||||
|
|
||||||
|
"react-reconciler": ["react-reconciler@0.33.0", "https://registry.npmmirror.com/react-reconciler/-/react-reconciler-0.33.0.tgz", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA=="],
|
||||||
|
|
||||||
|
"readdirp": ["readdirp@5.0.0", "https://registry.npmmirror.com/readdirp/-/readdirp-5.0.0.tgz", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
|
||||||
|
|
||||||
|
"require-directory": ["require-directory@2.1.1", "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
|
||||||
|
|
||||||
|
"require-from-string": ["require-from-string@2.0.2", "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
||||||
|
|
||||||
|
"require-main-filename": ["require-main-filename@2.0.0", "https://registry.npmmirror.com/require-main-filename/-/require-main-filename-2.0.0.tgz", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="],
|
||||||
|
|
||||||
|
"restore-cursor": ["restore-cursor@4.0.0", "https://registry.npmmirror.com/restore-cursor/-/restore-cursor-4.0.0.tgz", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg=="],
|
||||||
|
|
||||||
|
"retry": ["retry@0.12.0", "https://registry.npmmirror.com/retry/-/retry-0.12.0.tgz", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="],
|
||||||
|
|
||||||
|
"router": ["router@2.2.0", "https://registry.npmmirror.com/router/-/router-2.2.0.tgz", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
|
||||||
|
|
||||||
|
"safe-buffer": ["safe-buffer@5.2.1", "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||||
|
|
||||||
|
"safer-buffer": ["safer-buffer@2.1.2", "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||||
|
|
||||||
|
"scheduler": ["scheduler@0.27.0", "https://registry.npmmirror.com/scheduler/-/scheduler-0.27.0.tgz", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
||||||
|
|
||||||
|
"semver": ["semver@7.7.4", "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||||
|
|
||||||
|
"send": ["send@1.2.1", "https://registry.npmmirror.com/send/-/send-1.2.1.tgz", { "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.1", "mime-types": "^3.0.2", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.2" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="],
|
||||||
|
|
||||||
|
"serve-static": ["serve-static@2.2.1", "https://registry.npmmirror.com/serve-static/-/serve-static-2.2.1.tgz", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="],
|
||||||
|
|
||||||
|
"set-blocking": ["set-blocking@2.0.0", "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="],
|
||||||
|
|
||||||
|
"setprototypeof": ["setprototypeof@1.2.0", "https://registry.npmmirror.com/setprototypeof/-/setprototypeof-1.2.0.tgz", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
|
||||||
|
|
||||||
|
"shebang-command": ["shebang-command@2.0.0", "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
||||||
|
|
||||||
|
"shebang-regex": ["shebang-regex@3.0.0", "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
||||||
|
|
||||||
|
"shell-quote": ["shell-quote@1.8.3", "https://registry.npmmirror.com/shell-quote/-/shell-quote-1.8.3.tgz", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="],
|
||||||
|
|
||||||
|
"side-channel": ["side-channel@1.1.0", "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
|
||||||
|
|
||||||
|
"side-channel-list": ["side-channel-list@1.0.0", "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
|
||||||
|
|
||||||
|
"side-channel-map": ["side-channel-map@1.0.1", "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
|
||||||
|
|
||||||
|
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
|
||||||
|
|
||||||
|
"signal-exit": ["signal-exit@4.1.0", "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
|
||||||
|
|
||||||
|
"slice-ansi": ["slice-ansi@8.0.0", "https://registry.npmmirror.com/slice-ansi/-/slice-ansi-8.0.0.tgz", { "dependencies": { "ansi-styles": "^6.2.3", "is-fullwidth-code-point": "^5.1.0" } }, "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg=="],
|
||||||
|
|
||||||
|
"stack-utils": ["stack-utils@2.0.6", "https://registry.npmmirror.com/stack-utils/-/stack-utils-2.0.6.tgz", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="],
|
||||||
|
|
||||||
|
"statuses": ["statuses@2.0.2", "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
|
||||||
|
|
||||||
|
"string-width": ["string-width@8.2.0", "https://registry.npmmirror.com/string-width/-/string-width-8.2.0.tgz", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw=="],
|
||||||
|
|
||||||
|
"strip-ansi": ["strip-ansi@7.2.0", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.2.0.tgz", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
|
||||||
|
|
||||||
|
"strip-final-newline": ["strip-final-newline@4.0.0", "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-4.0.0.tgz", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="],
|
||||||
|
|
||||||
|
"strnum": ["strnum@2.2.2", "https://registry.npmmirror.com/strnum/-/strnum-2.2.2.tgz", {}, "sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA=="],
|
||||||
|
|
||||||
|
"supports-color": ["supports-color@10.2.2", "https://registry.npmmirror.com/supports-color/-/supports-color-10.2.2.tgz", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="],
|
||||||
|
|
||||||
|
"supports-hyperlinks": ["supports-hyperlinks@4.4.0", "https://registry.npmmirror.com/supports-hyperlinks/-/supports-hyperlinks-4.4.0.tgz", { "dependencies": { "has-flag": "^5.0.1", "supports-color": "^10.2.2" } }, "sha512-UKbpT93hN5Nr9go5UY7bopIB9YQlMz9nm/ct4IXt/irb5YRkn9WaqrOBJGZ5Pwvsd5FQzSVeYlGdXoCAPQZrPg=="],
|
||||||
|
|
||||||
|
"tagged-tag": ["tagged-tag@1.0.0", "https://registry.npmmirror.com/tagged-tag/-/tagged-tag-1.0.0.tgz", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="],
|
||||||
|
|
||||||
|
"terminal-size": ["terminal-size@4.0.1", "https://registry.npmmirror.com/terminal-size/-/terminal-size-4.0.1.tgz", {}, "sha512-avMLDQpUI9I5XFrklECw1ZEUPJhqzcwSWsyyI8blhRLT+8N1jLJWLWWYQpB2q2xthq8xDvjZPISVh53T/+CLYQ=="],
|
||||||
|
|
||||||
|
"toidentifier": ["toidentifier@1.0.1", "https://registry.npmmirror.com/toidentifier/-/toidentifier-1.0.1.tgz", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
|
||||||
|
|
||||||
|
"tree-kill": ["tree-kill@1.2.2", "https://registry.npmmirror.com/tree-kill/-/tree-kill-1.2.2.tgz", { "bin": "cli.js" }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],
|
||||||
|
|
||||||
|
"ts-algebra": ["ts-algebra@2.0.0", "https://registry.npmmirror.com/ts-algebra/-/ts-algebra-2.0.0.tgz", {}, "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw=="],
|
||||||
|
|
||||||
|
"tslib": ["tslib@2.8.1", "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
|
"type-fest": ["type-fest@5.5.0", "https://registry.npmmirror.com/type-fest/-/type-fest-5.5.0.tgz", { "dependencies": { "tagged-tag": "^1.0.0" } }, "sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g=="],
|
||||||
|
|
||||||
|
"type-is": ["type-is@2.0.1", "https://registry.npmmirror.com/type-is/-/type-is-2.0.1.tgz", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
|
||||||
|
|
||||||
|
"undici": ["undici@7.24.6", "https://registry.npmmirror.com/undici/-/undici-7.24.6.tgz", {}, "sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA=="],
|
||||||
|
|
||||||
|
"unicorn-magic": ["unicorn-magic@0.3.0", "https://registry.npmmirror.com/unicorn-magic/-/unicorn-magic-0.3.0.tgz", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="],
|
||||||
|
|
||||||
|
"unpipe": ["unpipe@1.0.0", "https://registry.npmmirror.com/unpipe/-/unpipe-1.0.0.tgz", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
|
||||||
|
|
||||||
|
"usehooks-ts": ["usehooks-ts@3.1.1", "https://registry.npmmirror.com/usehooks-ts/-/usehooks-ts-3.1.1.tgz", { "dependencies": { "lodash.debounce": "^4.0.8" }, "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-I4diPp9Cq6ieSUH2wu+fDAVQO43xwtulo+fKEidHUwZPnYImbtkTjzIJYcDcJqxgmX31GVqNFURodvcgHcW0pA=="],
|
||||||
|
|
||||||
|
"vary": ["vary@1.1.2", "https://registry.npmmirror.com/vary/-/vary-1.1.2.tgz", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
|
||||||
|
|
||||||
|
"vscode-jsonrpc": ["vscode-jsonrpc@8.2.1", "https://registry.npmmirror.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz", {}, "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ=="],
|
||||||
|
|
||||||
|
"vscode-languageserver-types": ["vscode-languageserver-types@3.17.5", "https://registry.npmmirror.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="],
|
||||||
|
|
||||||
|
"web-streams-polyfill": ["web-streams-polyfill@3.3.3", "https://registry.npmmirror.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
|
||||||
|
|
||||||
|
"which": ["which@2.0.2", "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||||
|
|
||||||
|
"which-module": ["which-module@2.0.1", "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz", {}, "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="],
|
||||||
|
|
||||||
|
"widest-line": ["widest-line@6.0.0", "https://registry.npmmirror.com/widest-line/-/widest-line-6.0.0.tgz", { "dependencies": { "string-width": "^8.1.0" } }, "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA=="],
|
||||||
|
|
||||||
|
"wrap-ansi": ["wrap-ansi@10.0.0", "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-10.0.0.tgz", { "dependencies": { "ansi-styles": "^6.2.3", "string-width": "^8.2.0", "strip-ansi": "^7.1.2" } }, "sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ=="],
|
||||||
|
|
||||||
|
"wrappy": ["wrappy@1.0.2", "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
|
||||||
|
|
||||||
|
"ws": ["ws@8.20.0", "https://registry.npmmirror.com/ws/-/ws-8.20.0.tgz", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA=="],
|
||||||
|
|
||||||
|
"xss": ["xss@1.0.15", "https://registry.npmmirror.com/xss/-/xss-1.0.15.tgz", { "dependencies": { "commander": "^2.20.3", "cssfilter": "0.0.10" }, "bin": "bin/xss" }, "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg=="],
|
||||||
|
|
||||||
|
"y18n": ["y18n@4.0.3", "https://registry.npmmirror.com/y18n/-/y18n-4.0.3.tgz", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="],
|
||||||
|
|
||||||
|
"yaml": ["yaml@2.8.3", "https://registry.npmmirror.com/yaml/-/yaml-2.8.3.tgz", { "bin": "bin.mjs" }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="],
|
||||||
|
|
||||||
|
"yargs": ["yargs@15.4.1", "https://registry.npmmirror.com/yargs/-/yargs-15.4.1.tgz", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="],
|
||||||
|
|
||||||
|
"yargs-parser": ["yargs-parser@18.1.3", "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-18.1.3.tgz", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="],
|
||||||
|
|
||||||
|
"yoctocolors": ["yoctocolors@2.1.2", "https://registry.npmmirror.com/yoctocolors/-/yoctocolors-2.1.2.tgz", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="],
|
||||||
|
|
||||||
|
"yoga-layout": ["yoga-layout@3.2.1", "https://registry.npmmirror.com/yoga-layout/-/yoga-layout-3.2.1.tgz", {}, "sha512-0LPOt3AxKqMdFBZA3HBAt/t/8vIKq7VaQYbuA8WxCgung+p9TVyKRYdpvCb80HcdTN2NkbIKbhNwKUfm3tQywQ=="],
|
||||||
|
|
||||||
|
"zod": ["zod@4.3.6", "https://registry.npmmirror.com/zod/-/zod-4.3.6.tgz", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||||
|
|
||||||
|
"zod-to-json-schema": ["zod-to-json-schema@3.25.2", "https://registry.npmmirror.com/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="],
|
||||||
|
|
||||||
|
"@anthropic-ai/sandbox-runtime/zod": ["zod@3.25.76", "https://registry.npmmirror.com/zod/-/zod-3.25.76.tgz", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||||
|
|
||||||
|
"@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "https://registry.npmmirror.com/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||||
|
|
||||||
|
"@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "https://registry.npmmirror.com/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||||
|
|
||||||
|
"@commander-js/extra-typings/commander": ["commander@14.0.3", "https://registry.npmmirror.com/commander/-/commander-14.0.3.tgz", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
|
||||||
|
|
||||||
|
"cliui/string-width": ["string-width@4.2.3", "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||||
|
|
||||||
|
"cliui/strip-ansi": ["strip-ansi@6.0.1", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||||
|
|
||||||
|
"cliui/wrap-ansi": ["wrap-ansi@6.2.0", "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
|
||||||
|
|
||||||
|
"form-data/mime-types": ["mime-types@2.1.35", "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||||
|
|
||||||
|
"gaxios/https-proxy-agent": ["https-proxy-agent@7.0.6", "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
|
||||||
|
|
||||||
|
"ink/cli-boxes": ["cli-boxes@3.0.0", "https://registry.npmmirror.com/cli-boxes/-/cli-boxes-3.0.0.tgz", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="],
|
||||||
|
|
||||||
|
"ink/signal-exit": ["signal-exit@3.0.7", "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||||
|
|
||||||
|
"ink/wrap-ansi": ["wrap-ansi@9.0.2", "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-9.0.2.tgz", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
|
||||||
|
|
||||||
|
"npm-run-path/path-key": ["path-key@4.0.0", "https://registry.npmmirror.com/path-key/-/path-key-4.0.0.tgz", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
|
||||||
|
|
||||||
|
"proper-lockfile/signal-exit": ["signal-exit@3.0.7", "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||||
|
|
||||||
|
"restore-cursor/signal-exit": ["signal-exit@3.0.7", "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
|
||||||
|
|
||||||
|
"xss/commander": ["commander@2.20.3", "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
|
||||||
|
|
||||||
|
"yargs/string-width": ["string-width@4.2.3", "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||||
|
|
||||||
|
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "https://registry.npmmirror.com/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
||||||
|
|
||||||
|
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "https://registry.npmmirror.com/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="],
|
||||||
|
|
||||||
|
"cliui/string-width/emoji-regex": ["emoji-regex@8.0.0", "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||||
|
|
||||||
|
"cliui/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||||
|
|
||||||
|
"cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
|
|
||||||
|
"cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||||
|
|
||||||
|
"form-data/mime-types/mime-db": ["mime-db@1.52.0", "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||||
|
|
||||||
|
"gaxios/https-proxy-agent/agent-base": ["agent-base@7.1.4", "https://registry.npmmirror.com/agent-base/-/agent-base-7.1.4.tgz", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
|
||||||
|
|
||||||
|
"ink/wrap-ansi/string-width": ["string-width@7.2.0", "https://registry.npmmirror.com/string-width/-/string-width-7.2.0.tgz", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||||
|
|
||||||
|
"yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||||
|
|
||||||
|
"yargs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||||
|
|
||||||
|
"yargs/string-width/strip-ansi": ["strip-ansi@6.0.1", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||||
|
|
||||||
|
"@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "https://registry.npmmirror.com/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
|
||||||
|
|
||||||
|
"@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "https://registry.npmmirror.com/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="],
|
||||||
|
|
||||||
|
"yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
|
}
|
||||||
|
}
|
||||||
241
docs/computer-use.en.md
Normal file
241
docs/computer-use.en.md
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
# Computer Use Guide
|
||||||
|
|
||||||
|
<p align="right"><a href="./computer-use.md">中文</a> | <strong>English</strong></p>
|
||||||
|
|
||||||
|
> **Modified Version**: This feature is a **heavily modified version** of the Computer Use (internal codename "Chicago") found in the leaked Claude Code source. The official implementation relies on Anthropic's private native modules (`@ant/computer-use-swift`, `@ant/computer-use-input`) that are not publicly available. We **replaced the entire underlying operation layer** with a Python bridge (`pyautogui` + `mss` + `pyobjc`), enabling anyone to run Computer Use on macOS.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Overview](#overview)
|
||||||
|
- [Supported Platforms](#supported-platforms)
|
||||||
|
- [How It Works](#how-it-works)
|
||||||
|
- [Quick Start](#quick-start)
|
||||||
|
- [Usage](#usage)
|
||||||
|
- [Security](#security)
|
||||||
|
- [Environment Variables](#environment-variables)
|
||||||
|
- [Technical Architecture](#technical-architecture)
|
||||||
|
- [Approaches We Tried](#approaches-we-tried)
|
||||||
|
- [Known Limitations](#known-limitations)
|
||||||
|
- [References and Credits](#references-and-credits)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Computer Use allows AI models to **directly control your computer** — taking screenshots, moving the mouse, clicking buttons, typing text, and managing application windows.
|
||||||
|
|
||||||
|
24 MCP tools are available:
|
||||||
|
|
||||||
|
| Category | Tools |
|
||||||
|
|----------|-------|
|
||||||
|
| Screenshot | `screenshot`, `zoom` |
|
||||||
|
| Mouse | `left_click`, `right_click`, `middle_click`, `double_click`, `triple_click`, `left_click_drag`, `mouse_move`, `left_mouse_down`, `left_mouse_up`, `cursor_position`, `scroll` |
|
||||||
|
| Keyboard | `type`, `key`, `hold_key` |
|
||||||
|
| Apps | `open_application`, `switch_display` |
|
||||||
|
| Permissions | `request_access`, `list_granted_applications` |
|
||||||
|
| Clipboard | `read_clipboard`, `write_clipboard` |
|
||||||
|
| Other | `wait`, `computer_batch` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Supported Platforms
|
||||||
|
|
||||||
|
| Platform | Architecture | Status | Notes |
|
||||||
|
|----------|-------------|--------|-------|
|
||||||
|
| macOS | Apple Silicon (M1/M2/M3/M4) | ✅ Fully supported | Recommended |
|
||||||
|
| macOS | Intel x86_64 | ✅ Fully supported | |
|
||||||
|
| Windows | Any | ⚠️ Theoretically possible | Core libs (`pyautogui` + `mss`) are cross-platform, but `pyobjc` parts (app management) need to be replaced with `win32com`. Not yet adapted |
|
||||||
|
| Linux | Any | ⚠️ Theoretically possible | Same as above — `pyobjc` needs to be replaced with `wmctrl` + `xdotool`. Not yet adapted |
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
- [Bun](https://bun.sh) >= 1.1.0
|
||||||
|
- Python >= 3.8 (venv and dependencies are auto-installed on first use)
|
||||||
|
- macOS permissions: Accessibility + Screen Recording
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
Computer Use operates through a **screenshot → analyze → act** feedback loop:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌────────────────────────────────────────────────────┐
|
||||||
|
│ AI Model (Claude / any Anthropic-protocol model) │
|
||||||
|
│ │
|
||||||
|
│ 1. Receives user request: "open Music app" │
|
||||||
|
│ 2. Calls screenshot tool → receives screen image │
|
||||||
|
│ 3. Model analyzes pixels, identifies UI elements │
|
||||||
|
│ → "search box is at (756, 342)" │
|
||||||
|
│ 4. Calls left_click { coordinate: [756, 342] } │
|
||||||
|
│ 5. Calls type { text: "search query" } │
|
||||||
|
│ 6. Calls screenshot again → verify → next step... │
|
||||||
|
└───────────────┬────────────────────────────────────┘
|
||||||
|
│ MCP Tool Call
|
||||||
|
▼
|
||||||
|
┌────────────────────────────────────────────────────┐
|
||||||
|
│ TypeScript Tool Layer (vendor/computer-use-mcp) │
|
||||||
|
│ - Security checks (app allowlist, TCC permissions) │
|
||||||
|
│ - Coordinate transformation │
|
||||||
|
│ - Tool dispatch → executor │
|
||||||
|
└───────────────┬────────────────────────────────────┘
|
||||||
|
│ callPythonHelper()
|
||||||
|
▼
|
||||||
|
┌────────────────────────────────────────────────────┐
|
||||||
|
│ Python Bridge (runtime/mac_helper.py) │
|
||||||
|
│ pyautogui.click(756, 342) ← mouse control │
|
||||||
|
│ mss.grab(monitor) ← screenshot │
|
||||||
|
│ NSWorkspace.open(bundleId) ← app management │
|
||||||
|
└────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key**: Coordinate analysis is performed entirely by the model's vision capabilities — it "sees" the screenshot like a human sees a screen, identifying buttons, text fields, and other UI elements directly from pixels.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Install dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Ensure Python 3 is available
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 --version # >= 3.8 required
|
||||||
|
```
|
||||||
|
|
||||||
|
> Python dependencies are **automatically installed** into `.runtime/venv/` on first Computer Use invocation.
|
||||||
|
|
||||||
|
### 3. Grant macOS permissions
|
||||||
|
|
||||||
|
**Accessibility:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
open "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"
|
||||||
|
```
|
||||||
|
|
||||||
|
Add your **terminal app** (iTerm, Terminal, Ghostty, etc.) to the allow list.
|
||||||
|
|
||||||
|
**Screen Recording:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
open "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture"
|
||||||
|
```
|
||||||
|
|
||||||
|
Add your terminal app as well. You may need to **restart your terminal** after granting permission.
|
||||||
|
|
||||||
|
### 4. Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bin/claude-haha
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Use
|
||||||
|
|
||||||
|
Just ask in natural language:
|
||||||
|
|
||||||
|
```
|
||||||
|
> Take a screenshot of my desktop
|
||||||
|
> Open Safari and search for something
|
||||||
|
> Type "hello" in the text editor
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
| Mechanism | Description |
|
||||||
|
|-----------|-------------|
|
||||||
|
| **App allowlist** | Each session requires explicit authorization for which apps Claude can interact with |
|
||||||
|
| **Concurrency lock** | Only one Claude session can use Computer Use at a time (file lock) |
|
||||||
|
| **Clipboard guard** | Original clipboard content is saved and restored when typing via clipboard |
|
||||||
|
| **Sensitive action gates** | System keyboard shortcuts require additional authorization |
|
||||||
|
|
||||||
|
> Note: Since we replaced the native modules with Python bridge, the global Escape hotkey abort and auto-hide features from the original implementation are not available. Use `Ctrl+C` to abort instead.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `CLAUDE_COMPUTER_USE_ENABLED` | `1` | Set to `0` to disable Computer Use |
|
||||||
|
| `CLAUDE_COMPUTER_USE_COORDINATE_MODE` | `pixels` | Coordinate mode: `pixels` or `normalized_0_100` |
|
||||||
|
| `CLAUDE_COMPUTER_USE_CLIPBOARD_PASTE` | `1` | Enable clipboard-based text input |
|
||||||
|
| `CLAUDE_COMPUTER_USE_MOUSE_ANIMATION` | `1` | Enable mouse animation |
|
||||||
|
| `CLAUDE_COMPUTER_USE_DEBUG` | `0` | Debug mode |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Technical Architecture
|
||||||
|
|
||||||
|
### Gate Bypass
|
||||||
|
|
||||||
|
The official Claude Code gates Computer Use behind three layers:
|
||||||
|
|
||||||
|
| Layer | Original Mechanism | Our Approach |
|
||||||
|
|-------|-------------------|--------------|
|
||||||
|
| Compile-time | `feature('CHICAGO_MCP')` (Bun macro) | Replaced with `true` |
|
||||||
|
| Subscription | `hasRequiredSubscription()` (Max/Pro only) | `getChicagoEnabled()` returns `true` directly |
|
||||||
|
| Remote config | GrowthBook `tengu_malort_pedway` | Same — no remote dependency |
|
||||||
|
| Default-disabled | `isDefaultDisabledBuiltin('computer-use')` | Returns `false` |
|
||||||
|
|
||||||
|
### Python Bridge
|
||||||
|
|
||||||
|
On first invocation, the bridge automatically:
|
||||||
|
1. Creates a Python virtual environment (`.runtime/venv/`)
|
||||||
|
2. Installs pip
|
||||||
|
3. Installs dependencies (`mss`, `Pillow`, `pyautogui`, `pyobjc-*`)
|
||||||
|
4. Validates via SHA256 hash (only reinstalls when `requirements.txt` changes)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Approaches We Tried
|
||||||
|
|
||||||
|
### Approach 1: Extract native .node modules from Claude Code binary ❌
|
||||||
|
|
||||||
|
Extracted `computer-use-swift.node` and `computer-use-input.node` from the installed Claude Code Mach-O binary. Synchronous methods worked, but async Swift methods (screenshot) hung due to N-API async incompatibility between Bun versions.
|
||||||
|
|
||||||
|
### Approach 2: Create empty stub packages ❌
|
||||||
|
|
||||||
|
Stub packages allowed compilation but provided no actual functionality.
|
||||||
|
|
||||||
|
### Approach 3: Python Bridge ✅ (current)
|
||||||
|
|
||||||
|
Replaced all native module calls with Python subprocess calls via `callPythonHelper()`. Zero binary dependencies, auto-bootstrapping, full functionality on any macOS.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Known Limitations
|
||||||
|
|
||||||
|
| Limitation | Description |
|
||||||
|
|------------|-------------|
|
||||||
|
| macOS only | Windows/Linux need `pyobjc` replacements |
|
||||||
|
| No global Escape abort | Original used CGEventTap; use `Ctrl+C` instead |
|
||||||
|
| No auto-hide windows | Original's `prepareDisplay` relied on Swift |
|
||||||
|
| Slightly higher latency | ~100ms Python process startup overhead per call |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## References and Credits
|
||||||
|
|
||||||
|
| Project | License | Contribution |
|
||||||
|
|---------|---------|-------------|
|
||||||
|
| [wimi321/macos-computer-use-skill](https://github.com/wimi321/macos-computer-use-skill) | MIT | Python bridge architecture, `mac_helper.py` runtime, executor adaptation |
|
||||||
|
| [domdomegg/computer-use-mcp](https://github.com/domdomegg/computer-use-mcp) | MIT | Independent Computer Use MCP server (nut.js based), used as reference |
|
||||||
|
| [paoloanzn/free-code](https://github.com/paoloanzn/free-code) | - | Feature flag system analysis |
|
||||||
|
| [oboard/claude-code-rev](https://github.com/oboard/claude-code-rev) | - | Early leaked source restoration, stub package reference |
|
||||||
|
|
||||||
|
### Underlying Libraries
|
||||||
|
|
||||||
|
| Library | Purpose |
|
||||||
|
|---------|---------|
|
||||||
|
| [pyautogui](https://github.com/asweigart/pyautogui) | Mouse and keyboard control |
|
||||||
|
| [mss](https://github.com/BoboTiG/python-mss) | Screenshot capture |
|
||||||
|
| [Pillow](https://github.com/python-pillow/Pillow) | Image processing and compression |
|
||||||
|
| [pyobjc](https://github.com/ronaldoussoren/pyobjc) | macOS Cocoa/Quartz framework bindings |
|
||||||
312
docs/computer-use.md
Normal file
312
docs/computer-use.md
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
# Computer Use 功能指南
|
||||||
|
|
||||||
|
<p align="right"><strong>中文</strong> | <a href="./computer-use.en.md">English</a></p>
|
||||||
|
|
||||||
|
> **魔改说明**:本功能是基于 Claude Code 泄露源码中的 Computer Use(内部代号 "Chicago")进行的**深度改造版本**。官方实现依赖 Anthropic 内部私有原生模块(`@ant/computer-use-swift`、`@ant/computer-use-input`),无法公开获取。我们**替换了整个底层操作层**,使用 Python bridge(`pyautogui` + `mss` + `pyobjc`)实现所有系统交互,使得任何人都可以在 macOS 上运行 Computer Use 功能。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
|
||||||
|
- [功能简介](#功能简介)
|
||||||
|
- [支持的设备与平台](#支持的设备与平台)
|
||||||
|
- [工作原理](#工作原理)
|
||||||
|
- [快速开始](#快速开始)
|
||||||
|
- [使用方式](#使用方式)
|
||||||
|
- [安全机制](#安全机制)
|
||||||
|
- [环境变量](#环境变量)
|
||||||
|
- [技术架构详解](#技术架构详解)
|
||||||
|
- [我们尝试过的方案](#我们尝试过的方案)
|
||||||
|
- [已知限制](#已知限制)
|
||||||
|
- [参考项目与致谢](#参考项目与致谢)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 功能简介
|
||||||
|
|
||||||
|
Computer Use 让 AI 模型能够**直接控制你的电脑**——截屏、移动鼠标、点击按钮、输入文字、管理应用窗口。
|
||||||
|
|
||||||
|
支持的操作(共 24 个 MCP 工具):
|
||||||
|
|
||||||
|
| 类别 | 工具 |
|
||||||
|
|------|------|
|
||||||
|
| 截屏 | `screenshot`、`zoom` |
|
||||||
|
| 鼠标 | `left_click`、`right_click`、`middle_click`、`double_click`、`triple_click`、`left_click_drag`、`mouse_move`、`left_mouse_down`、`left_mouse_up`、`cursor_position`、`scroll` |
|
||||||
|
| 键盘 | `type`、`key`、`hold_key` |
|
||||||
|
| 应用 | `open_application`、`switch_display` |
|
||||||
|
| 权限 | `request_access`、`list_granted_applications` |
|
||||||
|
| 剪贴板 | `read_clipboard`、`write_clipboard` |
|
||||||
|
| 其他 | `wait`、`computer_batch` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 支持的设备与平台
|
||||||
|
|
||||||
|
| 平台 | 芯片 | 状态 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| macOS | Apple Silicon (M1/M2/M3/M4) | ✅ 完整支持 | 推荐平台 |
|
||||||
|
| macOS | Intel x86_64 | ✅ 完整支持 | |
|
||||||
|
| Windows | 任意 | ⚠️ 理论可行 | `pyautogui` + `mss` 跨平台,但 `pyobjc` 部分(应用管理)需替换为 `win32com`,当前未适配 |
|
||||||
|
| Linux | 任意 | ⚠️ 理论可行 | 同上,需替换 `pyobjc` 为 `wmctrl` + `xdotool`,当前未适配 |
|
||||||
|
|
||||||
|
### 运行环境要求
|
||||||
|
|
||||||
|
- [Bun](https://bun.sh) >= 1.1.0
|
||||||
|
- Python >= 3.8(首次运行自动创建 venv 并安装依赖)
|
||||||
|
- macOS 系统权限:Accessibility(辅助功能)+ Screen Recording(屏幕录制)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 工作原理
|
||||||
|
|
||||||
|
Computer Use 的核心是一个**截图-分析-操作**的闭环:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────┐
|
||||||
|
│ AI 模型(Claude / 其他 Anthropic 协议模型) │
|
||||||
|
│ │
|
||||||
|
│ 1. 收到用户请求 "打开网易云搜索喜欢你" │
|
||||||
|
│ 2. 调用 screenshot 工具 → 收到屏幕截图 │
|
||||||
|
│ 3. 模型分析截图像素,识别 UI 元素位置 │
|
||||||
|
│ → "搜索框在 (756, 342)" │
|
||||||
|
│ 4. 调用 left_click { coordinate: [756, 342] } │
|
||||||
|
│ 5. 调用 type { text: "喜欢你" } │
|
||||||
|
│ 6. 再次 screenshot → 确认结果 → 下一步... │
|
||||||
|
└──────────────┬───────────────────────────────┘
|
||||||
|
│ MCP Tool Call
|
||||||
|
▼
|
||||||
|
┌──────────────────────────────────────────────┐
|
||||||
|
│ TypeScript 工具层 │
|
||||||
|
│ (vendor/computer-use-mcp) │
|
||||||
|
│ │
|
||||||
|
│ - 安全检查(应用白名单、TCC 权限) │
|
||||||
|
│ - 坐标模式转换(pixels / normalized) │
|
||||||
|
│ - 工具分发 → executor │
|
||||||
|
└──────────────┬───────────────────────────────┘
|
||||||
|
│ callPythonHelper()
|
||||||
|
▼
|
||||||
|
┌──────────────────────────────────────────────┐
|
||||||
|
│ Python Bridge │
|
||||||
|
│ (runtime/mac_helper.py) │
|
||||||
|
│ │
|
||||||
|
│ pyautogui.click(756, 342) ← 鼠标控制 │
|
||||||
|
│ mss.grab(monitor) ← 截图 │
|
||||||
|
│ NSWorkspace.open(bundleId) ← 应用管理 │
|
||||||
|
└──────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键:坐标分析完全由模型的视觉能力完成**——模型"看"截图就像人看屏幕一样,直接从像素中识别按钮、输入框等 UI 元素的位置。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 1. 安装依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 确保 Python 3 可用
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 --version # 需要 >= 3.8
|
||||||
|
```
|
||||||
|
|
||||||
|
> Python 依赖会在首次使用 Computer Use 时**自动安装**到 `.runtime/venv/`,无需手动操作。
|
||||||
|
|
||||||
|
### 3. 授予 macOS 权限
|
||||||
|
|
||||||
|
#### Accessibility(辅助功能)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
open "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"
|
||||||
|
```
|
||||||
|
|
||||||
|
将你的**终端应用**(如 iTerm、Terminal、Ghostty 等)添加到允许列表。
|
||||||
|
|
||||||
|
#### Screen Recording(屏幕录制)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
open "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture"
|
||||||
|
```
|
||||||
|
|
||||||
|
同样添加你的终端应用。授权后可能需要**重启终端**。
|
||||||
|
|
||||||
|
### 4. 启动
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bin/claude-haha
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 使用
|
||||||
|
|
||||||
|
在对话中用自然语言请求即可:
|
||||||
|
|
||||||
|
```
|
||||||
|
> 帮我打开网易云音乐,搜索一首歌
|
||||||
|
> 截个屏看看当前桌面
|
||||||
|
> 帮我在 VS Code 里打开终端
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 使用方式
|
||||||
|
|
||||||
|
首次使用 Computer Use 时,系统会弹出**应用授权对话框**,你需要选择允许 Claude 操作的应用。
|
||||||
|
|
||||||
|
- 模型会先调用 `request_access` 请求权限
|
||||||
|
- 你在终端中确认允许哪些应用
|
||||||
|
- 之后模型就可以截图、点击、输入了
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 安全机制
|
||||||
|
|
||||||
|
| 机制 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| **应用白名单** | 每次会话需要明确授权允许操作的应用 |
|
||||||
|
| **并发保护** | 同一时间只有一个 Claude 会话可使用 Computer Use(文件锁机制) |
|
||||||
|
| **剪贴板保护** | 通过剪贴板输入文本时会自动保存和恢复原始剪贴板内容 |
|
||||||
|
| **操作确认** | 敏感操作(如系统快捷键)需要额外授权 |
|
||||||
|
|
||||||
|
> 注意:由于底层改为 Python bridge,原生方案中的全局 Escape 快捷键中止和操作前自动隐藏应用功能暂不可用。可使用 `Ctrl+C` 中止。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 环境变量
|
||||||
|
|
||||||
|
| 变量 | 默认值 | 说明 |
|
||||||
|
|------|--------|------|
|
||||||
|
| `CLAUDE_COMPUTER_USE_ENABLED` | `1` | 设为 `0` 可禁用 Computer Use |
|
||||||
|
| `CLAUDE_COMPUTER_USE_COORDINATE_MODE` | `pixels` | 坐标模式:`pixels` 或 `normalized_0_100` |
|
||||||
|
| `CLAUDE_COMPUTER_USE_CLIPBOARD_PASTE` | `1` | 是否启用剪贴板粘贴输入 |
|
||||||
|
| `CLAUDE_COMPUTER_USE_MOUSE_ANIMATION` | `1` | 是否启用鼠标动画 |
|
||||||
|
| `CLAUDE_COMPUTER_USE_HIDE_BEFORE_ACTION` | `0` | 操作前是否隐藏其他窗口 |
|
||||||
|
| `CLAUDE_COMPUTER_USE_DEBUG` | `0` | 调试模式 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 技术架构详解
|
||||||
|
|
||||||
|
### 整体分层
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── vendor/computer-use-mcp/ ← MCP 工具定义与分发(12 个文件)
|
||||||
|
│ ├── tools.ts ← 24 个工具的 schema 定义
|
||||||
|
│ ├── toolCalls.ts ← 安全检查 + 工具分发
|
||||||
|
│ ├── mcpServer.ts ← MCP 服务器创建
|
||||||
|
│ ├── types.ts ← 全部类型定义
|
||||||
|
│ └── ...
|
||||||
|
├── utils/computerUse/
|
||||||
|
│ ├── executor.ts ← 执行器(调用 Python bridge)
|
||||||
|
│ ├── pythonBridge.ts ← Python 子进程管理
|
||||||
|
│ ├── hostAdapter.ts ← 权限检查适配器
|
||||||
|
│ ├── gates.ts ← 功能开关(已绕过灰度)
|
||||||
|
│ ├── wrapper.tsx ← MCP 工具覆写层
|
||||||
|
│ ├── setup.ts ← MCP 配置初始化
|
||||||
|
│ └── ...
|
||||||
|
└── runtime/
|
||||||
|
├── mac_helper.py ← Python 实现(659 行)
|
||||||
|
└── requirements.txt ← Python 依赖
|
||||||
|
```
|
||||||
|
|
||||||
|
### 灰度控制绕过
|
||||||
|
|
||||||
|
官方 Claude Code 中 Computer Use 通过三层门控限制访问:
|
||||||
|
|
||||||
|
| 层级 | 原始机制 | 我们的处理 |
|
||||||
|
|------|----------|-----------|
|
||||||
|
| 编译时 | `feature('CHICAGO_MCP')` (Bun 编译宏) | 替换为 `true` |
|
||||||
|
| 订阅检查 | `hasRequiredSubscription()` (Max/Pro) | `getChicagoEnabled()` 直接返回 `true` |
|
||||||
|
| 远程配置 | GrowthBook `tengu_malort_pedway` | 同上,不再依赖远程配置 |
|
||||||
|
| 默认禁用 | `isDefaultDisabledBuiltin('computer-use')` | `isDefaultDisabledBuiltin()` 返回 `false` |
|
||||||
|
|
||||||
|
### Python Bridge 工作机制
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// pythonBridge.ts
|
||||||
|
async function callPythonHelper<T>(command: string, payload: object): Promise<T> {
|
||||||
|
await ensureBootstrapped() // 首次调用自动创建 venv + pip install
|
||||||
|
|
||||||
|
// 调用: python3 runtime/mac_helper.py <command> --payload '{...}'
|
||||||
|
const result = execFile(pythonBin, ['mac_helper.py', command, '--payload', JSON.stringify(payload)])
|
||||||
|
|
||||||
|
return JSON.parse(result.stdout) // { ok: true, result: T }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
首次运行自动完成:
|
||||||
|
1. 创建 Python 虚拟环境 (`.runtime/venv/`)
|
||||||
|
2. 安装 pip
|
||||||
|
3. 安装依赖 (`mss`, `Pillow`, `pyautogui`, `pyobjc-*`)
|
||||||
|
4. SHA256 哈希验证(仅 requirements.txt 变更时重新安装)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 我们尝试过的方案
|
||||||
|
|
||||||
|
### 方案一:从 Claude Code 二进制提取原生 .node 模块 ❌
|
||||||
|
|
||||||
|
**思路**:从已安装的 Claude Code 二进制 (`~/.local/share/claude/versions/2.1.91`,189MB Mach-O) 中定位并提取嵌入的原生 NAPI 模块。
|
||||||
|
|
||||||
|
**实施**:
|
||||||
|
- 成功从 Bun `$bunfs` 虚拟文件系统中提取了 `computer-use-swift.node` (ARM64 424KB + x64 430KB) 和 `computer-use-input.node` (ARM64 836KB + x64 821KB)
|
||||||
|
- 同步方法(TCC 权限检查、显示枚举)正常工作
|
||||||
|
- 创建了 npm 包装包并通过 workspace 注册
|
||||||
|
|
||||||
|
**失败原因**:
|
||||||
|
- Swift 异步方法(`screenshot.captureExcluding`)的 continuation 永远不会 resume
|
||||||
|
- 根因:提取的 .node 文件是针对 Claude Code 内置的 Bun 运行时编译的,与用户系统的 Bun 版本的 N-API 异步实现不兼容
|
||||||
|
- 错误信息:`SWIFT TASK CONTINUATION MISUSE: captureScreenWithExclusion leaked its continuation without resuming it`
|
||||||
|
|
||||||
|
### 方案二:创建空 Stub 包 ❌
|
||||||
|
|
||||||
|
**思路**:为 `@ant/computer-use-mcp`、`@ant/computer-use-input`、`@ant/computer-use-swift` 创建最小化的 stub 包,使代码能编译加载。
|
||||||
|
|
||||||
|
**失败原因**:代码能编译但 MCP 服务器注册后无法执行任何实际操作——截图、点击等全部报错。
|
||||||
|
|
||||||
|
### 方案三:Python Bridge 替代原生模块 ✅(当前方案)
|
||||||
|
|
||||||
|
**思路**:参考 [wimi321/macos-computer-use-skill](https://github.com/wimi321/macos-computer-use-skill),用 Python 子进程替代所有原生模块调用。
|
||||||
|
|
||||||
|
**优势**:
|
||||||
|
- 零二进制依赖,不依赖特定 Bun/Node 版本
|
||||||
|
- 纯 Python 实现,首次运行自动安装
|
||||||
|
- 截图、鼠标、键盘、应用管理全部可用
|
||||||
|
- macOS ARM64 + x86_64 均支持
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 已知限制
|
||||||
|
|
||||||
|
| 限制 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 仅 macOS | Windows/Linux 需要适配 `pyobjc` 部分 |
|
||||||
|
| 无全局 Escape 中止 | 原生方案用 CGEventTap 实现,Python 版暂不支持,用 `Ctrl+C` 代替 |
|
||||||
|
| 操作前不自动隐藏窗口 | 原生方案的 `prepareDisplay` 依赖 Swift,Python 版未实现 |
|
||||||
|
| 性能略低 | Python 进程启动 ~100ms 开销,但模型思考时间通常是秒级,用户感知不到 |
|
||||||
|
| 像素验证关闭 | `pixelValidation` 默认关闭 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 参考项目与致谢
|
||||||
|
|
||||||
|
本功能的实现参考了以下开源项目,在此致以感谢:
|
||||||
|
|
||||||
|
| 项目 | 许可证 | 贡献 |
|
||||||
|
|------|--------|------|
|
||||||
|
| [wimi321/macos-computer-use-skill](https://github.com/wimi321/macos-computer-use-skill) | MIT | Python bridge 架构、`mac_helper.py` 运行时、`executor.ts` 适配方案。该项目从 Claude Code 工作流中提取了可复用的 TypeScript 逻辑,并用完全公开的 Python 库替代了私有原生模块 |
|
||||||
|
| [domdomegg/computer-use-mcp](https://github.com/domdomegg/computer-use-mcp) | MIT | 独立的 Computer Use MCP 服务器实现(基于 nut.js),跨平台可用。在方案调研阶段提供了参考 |
|
||||||
|
| [paoloanzn/free-code](https://github.com/paoloanzn/free-code) | - | Feature flag 系统分析和构建系统参考 |
|
||||||
|
| [oboard/claude-code-rev](https://github.com/oboard/claude-code-rev) | - | 泄露源码的早期恢复工作,提供了 stub 包的参考实现 |
|
||||||
|
|
||||||
|
### 底层依赖
|
||||||
|
|
||||||
|
| 库 | 用途 |
|
||||||
|
|----|------|
|
||||||
|
| [pyautogui](https://github.com/asweigart/pyautogui) | 鼠标和键盘控制 |
|
||||||
|
| [mss](https://github.com/BoboTiG/python-mss) | 屏幕截图 |
|
||||||
|
| [Pillow](https://github.com/python-pillow/Pillow) | 图像处理和压缩 |
|
||||||
|
| [pyobjc](https://github.com/ronaldoussoren/pyobjc) | macOS Cocoa/Quartz 框架绑定(应用管理、显示枚举) |
|
||||||
659
runtime/mac_helper.py
Executable file
659
runtime/mac_helper.py
Executable file
@ -0,0 +1,659 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from io import BytesIO
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import mss
|
||||||
|
from AppKit import NSWorkspace, NSPasteboard, NSPasteboardTypeString, NSURL
|
||||||
|
from PIL import Image
|
||||||
|
from Quartz import (
|
||||||
|
CGDisplayBounds,
|
||||||
|
CGDisplayIsMain,
|
||||||
|
CGDisplayModeGetPixelHeight,
|
||||||
|
CGDisplayModeGetPixelWidth,
|
||||||
|
CGDisplayPixelsHigh,
|
||||||
|
CGDisplayPixelsWide,
|
||||||
|
CGGetActiveDisplayList,
|
||||||
|
CGMainDisplayID,
|
||||||
|
CGWindowListCopyWindowInfo,
|
||||||
|
CGRectContainsPoint,
|
||||||
|
CGRectIntersection,
|
||||||
|
CGPointMake,
|
||||||
|
kCGNullWindowID,
|
||||||
|
kCGWindowBounds,
|
||||||
|
kCGWindowIsOnscreen,
|
||||||
|
kCGWindowLayer,
|
||||||
|
kCGWindowListExcludeDesktopElements,
|
||||||
|
kCGWindowListOptionOnScreenOnly,
|
||||||
|
kCGWindowName,
|
||||||
|
kCGWindowOwnerName,
|
||||||
|
)
|
||||||
|
|
||||||
|
os.environ.setdefault("PYTHONDONTWRITEBYTECODE", "1")
|
||||||
|
os.environ.setdefault("PYAUTOGUI_HIDE_SUPPORT_PROMPT", "1")
|
||||||
|
|
||||||
|
import pyautogui # noqa: E402
|
||||||
|
|
||||||
|
pyautogui.FAILSAFE = False
|
||||||
|
pyautogui.PAUSE = 0
|
||||||
|
|
||||||
|
KEY_MAP = {
|
||||||
|
"a": "a",
|
||||||
|
"b": "b",
|
||||||
|
"c": "c",
|
||||||
|
"d": "d",
|
||||||
|
"e": "e",
|
||||||
|
"f": "f",
|
||||||
|
"g": "g",
|
||||||
|
"h": "h",
|
||||||
|
"i": "i",
|
||||||
|
"j": "j",
|
||||||
|
"k": "k",
|
||||||
|
"l": "l",
|
||||||
|
"m": "m",
|
||||||
|
"n": "n",
|
||||||
|
"o": "o",
|
||||||
|
"p": "p",
|
||||||
|
"q": "q",
|
||||||
|
"r": "r",
|
||||||
|
"s": "s",
|
||||||
|
"t": "t",
|
||||||
|
"u": "u",
|
||||||
|
"v": "v",
|
||||||
|
"w": "w",
|
||||||
|
"x": "x",
|
||||||
|
"y": "y",
|
||||||
|
"z": "z",
|
||||||
|
"0": "0",
|
||||||
|
"1": "1",
|
||||||
|
"2": "2",
|
||||||
|
"3": "3",
|
||||||
|
"4": "4",
|
||||||
|
"5": "5",
|
||||||
|
"6": "6",
|
||||||
|
"7": "7",
|
||||||
|
"8": "8",
|
||||||
|
"9": "9",
|
||||||
|
"cmd": "command",
|
||||||
|
"command": "command",
|
||||||
|
"meta": "command",
|
||||||
|
"super": "command",
|
||||||
|
"ctrl": "ctrl",
|
||||||
|
"control": "ctrl",
|
||||||
|
"shift": "shift",
|
||||||
|
"alt": "option",
|
||||||
|
"option": "option",
|
||||||
|
"opt": "option",
|
||||||
|
"fn": "fn",
|
||||||
|
"escape": "esc",
|
||||||
|
"esc": "esc",
|
||||||
|
"enter": "enter",
|
||||||
|
"return": "enter",
|
||||||
|
"tab": "tab",
|
||||||
|
"space": "space",
|
||||||
|
"backspace": "backspace",
|
||||||
|
"delete": "delete",
|
||||||
|
"forwarddelete": "delete",
|
||||||
|
"up": "up",
|
||||||
|
"down": "down",
|
||||||
|
"left": "left",
|
||||||
|
"right": "right",
|
||||||
|
"home": "home",
|
||||||
|
"end": "end",
|
||||||
|
"pageup": "pageup",
|
||||||
|
"pagedown": "pagedown",
|
||||||
|
"capslock": "capslock",
|
||||||
|
"f1": "f1",
|
||||||
|
"f2": "f2",
|
||||||
|
"f3": "f3",
|
||||||
|
"f4": "f4",
|
||||||
|
"f5": "f5",
|
||||||
|
"f6": "f6",
|
||||||
|
"f7": "f7",
|
||||||
|
"f8": "f8",
|
||||||
|
"f9": "f9",
|
||||||
|
"f10": "f10",
|
||||||
|
"f11": "f11",
|
||||||
|
"f12": "f12",
|
||||||
|
"-": "minus",
|
||||||
|
"=": "equals",
|
||||||
|
"[": "[",
|
||||||
|
"]": "]",
|
||||||
|
"\\": "\\",
|
||||||
|
";": ";",
|
||||||
|
"'": "'",
|
||||||
|
",": ",",
|
||||||
|
".": ".",
|
||||||
|
"/": "/",
|
||||||
|
"`": "`",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_key(name: str) -> str:
|
||||||
|
key = name.strip().lower()
|
||||||
|
if key not in KEY_MAP:
|
||||||
|
raise ValueError(f"Unsupported key: {name}")
|
||||||
|
return KEY_MAP[key]
|
||||||
|
|
||||||
|
|
||||||
|
def json_output(payload: dict[str, Any]) -> None:
|
||||||
|
sys.stdout.write(json.dumps(payload, ensure_ascii=False))
|
||||||
|
sys.stdout.write("\n")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
|
def error_output(message: str, code: str = "runtime_error") -> None:
|
||||||
|
json_output({"ok": False, "error": {"code": code, "message": message}})
|
||||||
|
|
||||||
|
|
||||||
|
def bool_env(name: str, default: bool = False) -> bool:
|
||||||
|
value = os.environ.get(name)
|
||||||
|
if value is None:
|
||||||
|
return default
|
||||||
|
return value not in {"0", "false", "False", ""}
|
||||||
|
|
||||||
|
|
||||||
|
def run_osascript(script: str) -> str:
|
||||||
|
result = subprocess.run(
|
||||||
|
["osascript", "-e", script],
|
||||||
|
text=True,
|
||||||
|
capture_output=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
if result.returncode != 0:
|
||||||
|
raise RuntimeError(result.stderr.strip() or result.stdout.strip() or "osascript failed")
|
||||||
|
return result.stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def get_displays() -> list[dict[str, Any]]:
|
||||||
|
max_displays = 32
|
||||||
|
err, active, count = CGGetActiveDisplayList(max_displays, None, None)
|
||||||
|
if err != 0:
|
||||||
|
raise RuntimeError(f"CGGetActiveDisplayList failed: {err}")
|
||||||
|
displays: list[dict[str, Any]] = []
|
||||||
|
main_id = CGMainDisplayID()
|
||||||
|
for idx, display_id in enumerate(active[:count]):
|
||||||
|
bounds = CGDisplayBounds(display_id)
|
||||||
|
mode = None
|
||||||
|
try:
|
||||||
|
from Quartz import CGDisplayCopyDisplayMode
|
||||||
|
mode = CGDisplayCopyDisplayMode(display_id)
|
||||||
|
except Exception:
|
||||||
|
mode = None
|
||||||
|
physical_width = int(CGDisplayPixelsWide(display_id))
|
||||||
|
physical_height = int(CGDisplayPixelsHigh(display_id))
|
||||||
|
logical_width = int(bounds.size.width)
|
||||||
|
logical_height = int(bounds.size.height)
|
||||||
|
if mode is not None:
|
||||||
|
mode_w = int(CGDisplayModeGetPixelWidth(mode))
|
||||||
|
mode_h = int(CGDisplayModeGetPixelHeight(mode))
|
||||||
|
physical_width = mode_w or physical_width
|
||||||
|
physical_height = mode_h or physical_height
|
||||||
|
scale_factor = physical_width / logical_width if logical_width else 1
|
||||||
|
name = f"Display {idx + 1}"
|
||||||
|
displays.append(
|
||||||
|
{
|
||||||
|
"id": int(display_id),
|
||||||
|
"displayId": int(display_id),
|
||||||
|
"width": logical_width,
|
||||||
|
"height": logical_height,
|
||||||
|
"scaleFactor": scale_factor,
|
||||||
|
"originX": int(bounds.origin.x),
|
||||||
|
"originY": int(bounds.origin.y),
|
||||||
|
"isPrimary": bool(display_id == main_id or CGDisplayIsMain(display_id)),
|
||||||
|
"name": name,
|
||||||
|
"label": name,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return displays
|
||||||
|
|
||||||
|
|
||||||
|
def choose_display(display_id: int | None) -> dict[str, Any]:
|
||||||
|
displays = get_displays()
|
||||||
|
if not displays:
|
||||||
|
raise RuntimeError("No active displays found")
|
||||||
|
if display_id is None:
|
||||||
|
for display in displays:
|
||||||
|
if display["isPrimary"]:
|
||||||
|
return display
|
||||||
|
return displays[0]
|
||||||
|
for display in displays:
|
||||||
|
if display["displayId"] == display_id or display["id"] == display_id:
|
||||||
|
return display
|
||||||
|
raise RuntimeError(f"Unknown display: {display_id}")
|
||||||
|
|
||||||
|
|
||||||
|
def capture_display(display_id: int | None, resize: tuple[int, int] | None = None) -> dict[str, Any]:
|
||||||
|
display = choose_display(display_id)
|
||||||
|
monitor = {
|
||||||
|
"left": display["originX"],
|
||||||
|
"top": display["originY"],
|
||||||
|
"width": display["width"],
|
||||||
|
"height": display["height"],
|
||||||
|
}
|
||||||
|
with mss.mss() as sct:
|
||||||
|
raw = sct.grab(monitor)
|
||||||
|
image = Image.frombytes("RGB", raw.size, raw.rgb)
|
||||||
|
if resize:
|
||||||
|
image = image.resize(resize, Image.Resampling.LANCZOS)
|
||||||
|
buffer = BytesIO()
|
||||||
|
image.save(buffer, format="JPEG", quality=75, optimize=True)
|
||||||
|
base64_data = base64.b64encode(buffer.getvalue()).decode("ascii")
|
||||||
|
return {
|
||||||
|
"base64": base64_data,
|
||||||
|
"width": image.width,
|
||||||
|
"height": image.height,
|
||||||
|
"displayWidth": display["width"],
|
||||||
|
"displayHeight": display["height"],
|
||||||
|
"displayId": display["displayId"],
|
||||||
|
"originX": display["originX"],
|
||||||
|
"originY": display["originY"],
|
||||||
|
"display": display,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def capture_region(region: dict[str, int], resize: tuple[int, int] | None = None) -> dict[str, Any]:
|
||||||
|
with mss.mss() as sct:
|
||||||
|
raw = sct.grab(region)
|
||||||
|
image = Image.frombytes("RGB", raw.size, raw.rgb)
|
||||||
|
if resize:
|
||||||
|
image = image.resize(resize, Image.Resampling.LANCZOS)
|
||||||
|
buffer = BytesIO()
|
||||||
|
image.save(buffer, format="JPEG", quality=75, optimize=True)
|
||||||
|
base64_data = base64.b64encode(buffer.getvalue()).decode("ascii")
|
||||||
|
return {"base64": base64_data, "width": image.width, "height": image.height}
|
||||||
|
|
||||||
|
|
||||||
|
def list_windows() -> list[dict[str, Any]]:
|
||||||
|
windows = CGWindowListCopyWindowInfo(
|
||||||
|
kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements,
|
||||||
|
kCGNullWindowID,
|
||||||
|
)
|
||||||
|
out: list[dict[str, Any]] = []
|
||||||
|
for window in windows or []:
|
||||||
|
if int(window.get(kCGWindowLayer, 0)) != 0:
|
||||||
|
continue
|
||||||
|
if not bool(window.get(kCGWindowIsOnscreen, True)):
|
||||||
|
continue
|
||||||
|
bounds = window.get(kCGWindowBounds) or {}
|
||||||
|
width = int(bounds.get("Width", 0))
|
||||||
|
height = int(bounds.get("Height", 0))
|
||||||
|
if width <= 1 or height <= 1:
|
||||||
|
continue
|
||||||
|
out.append(
|
||||||
|
{
|
||||||
|
"ownerName": window.get(kCGWindowOwnerName, "") or "",
|
||||||
|
"title": window.get(kCGWindowName, "") or "",
|
||||||
|
"bounds": {
|
||||||
|
"x": int(bounds.get("X", 0)),
|
||||||
|
"y": int(bounds.get("Y", 0)),
|
||||||
|
"width": width,
|
||||||
|
"height": height,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def bundle_id_to_app(bundle_id: str):
|
||||||
|
return NSWorkspace.sharedWorkspace().URLForApplicationWithBundleIdentifier_(bundle_id)
|
||||||
|
|
||||||
|
|
||||||
|
def installed_apps() -> list[dict[str, Any]]:
|
||||||
|
search_roots = [
|
||||||
|
Path("/Applications"),
|
||||||
|
Path.home() / "Applications",
|
||||||
|
Path("/System/Applications"),
|
||||||
|
Path("/System/Applications/Utilities"),
|
||||||
|
]
|
||||||
|
results: dict[str, dict[str, Any]] = {}
|
||||||
|
workspace = NSWorkspace.sharedWorkspace()
|
||||||
|
for root in search_roots:
|
||||||
|
if not root.exists():
|
||||||
|
continue
|
||||||
|
for app in root.rglob("*.app"):
|
||||||
|
try:
|
||||||
|
bundle = workspace.bundleIdentifierForURL_(NSURL.fileURLWithPath_(str(app)))
|
||||||
|
except Exception:
|
||||||
|
bundle = None
|
||||||
|
if not bundle:
|
||||||
|
try:
|
||||||
|
url = workspace.URLForApplicationWithBundleIdentifier_(str(app))
|
||||||
|
bundle = workspace.bundleIdentifierForURL_(url) if url else None
|
||||||
|
except Exception:
|
||||||
|
bundle = None
|
||||||
|
info_plist = app / "Contents/Info.plist"
|
||||||
|
display_name = app.stem
|
||||||
|
if info_plist.exists():
|
||||||
|
try:
|
||||||
|
import plistlib
|
||||||
|
with info_plist.open("rb") as f:
|
||||||
|
plist = plistlib.load(f)
|
||||||
|
bundle = bundle or plist.get("CFBundleIdentifier")
|
||||||
|
display_name = plist.get("CFBundleDisplayName") or plist.get("CFBundleName") or display_name
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
if not bundle or bundle in results:
|
||||||
|
continue
|
||||||
|
results[bundle] = {
|
||||||
|
"bundleId": str(bundle),
|
||||||
|
"displayName": str(display_name),
|
||||||
|
"path": str(app),
|
||||||
|
}
|
||||||
|
return sorted(results.values(), key=lambda item: item["displayName"].lower())
|
||||||
|
|
||||||
|
|
||||||
|
def running_apps() -> list[dict[str, Any]]:
|
||||||
|
apps = []
|
||||||
|
seen = set()
|
||||||
|
for app in NSWorkspace.sharedWorkspace().runningApplications() or []:
|
||||||
|
bundle_id = app.bundleIdentifier()
|
||||||
|
if not bundle_id or bundle_id in seen:
|
||||||
|
continue
|
||||||
|
seen.add(bundle_id)
|
||||||
|
name = app.localizedName() or bundle_id
|
||||||
|
apps.append({"bundleId": str(bundle_id), "displayName": str(name)})
|
||||||
|
return sorted(apps, key=lambda item: item["displayName"].lower())
|
||||||
|
|
||||||
|
|
||||||
|
def app_display_name(bundle_id: str) -> str | None:
|
||||||
|
for app in NSWorkspace.sharedWorkspace().runningApplications() or []:
|
||||||
|
if app.bundleIdentifier() == bundle_id:
|
||||||
|
return str(app.localizedName() or bundle_id)
|
||||||
|
for app in installed_apps():
|
||||||
|
if app["bundleId"] == bundle_id:
|
||||||
|
return str(app["displayName"])
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def frontmost_app() -> dict[str, str] | None:
|
||||||
|
app = NSWorkspace.sharedWorkspace().frontmostApplication()
|
||||||
|
if not app:
|
||||||
|
return None
|
||||||
|
bundle_id = app.bundleIdentifier()
|
||||||
|
if not bundle_id:
|
||||||
|
return None
|
||||||
|
return {
|
||||||
|
"bundleId": str(bundle_id),
|
||||||
|
"displayName": str(app.localizedName() or bundle_id),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def app_under_point(x: int, y: int) -> dict[str, str] | None:
|
||||||
|
point = CGPointMake(x, y)
|
||||||
|
running_by_name = {
|
||||||
|
str(app.localizedName() or app.bundleIdentifier()): str(app.bundleIdentifier())
|
||||||
|
for app in NSWorkspace.sharedWorkspace().runningApplications() or []
|
||||||
|
if app.bundleIdentifier()
|
||||||
|
}
|
||||||
|
for window in list_windows():
|
||||||
|
bounds = window["bounds"]
|
||||||
|
rect = ((bounds["x"], bounds["y"]), (bounds["width"], bounds["height"]))
|
||||||
|
if CGRectContainsPoint(rect, point):
|
||||||
|
owner = window["ownerName"]
|
||||||
|
bundle = running_by_name.get(owner)
|
||||||
|
if bundle:
|
||||||
|
return {"bundleId": bundle, "displayName": str(owner)}
|
||||||
|
return frontmost_app()
|
||||||
|
|
||||||
|
|
||||||
|
def find_window_displays(bundle_ids: list[str]) -> list[dict[str, Any]]:
|
||||||
|
if not bundle_ids:
|
||||||
|
return []
|
||||||
|
displays = get_displays()
|
||||||
|
names_by_bundle = {
|
||||||
|
bundle_id: app_display_name(bundle_id) or bundle_id for bundle_id in bundle_ids
|
||||||
|
}
|
||||||
|
windows = list_windows()
|
||||||
|
result = []
|
||||||
|
for bundle_id in bundle_ids:
|
||||||
|
target_name = names_by_bundle.get(bundle_id)
|
||||||
|
display_ids: set[int] = set()
|
||||||
|
for window in windows:
|
||||||
|
owner = window["ownerName"]
|
||||||
|
if not owner:
|
||||||
|
continue
|
||||||
|
if target_name and owner != target_name:
|
||||||
|
continue
|
||||||
|
if not target_name and owner != bundle_id:
|
||||||
|
continue
|
||||||
|
wx = window["bounds"]["x"]
|
||||||
|
wy = window["bounds"]["y"]
|
||||||
|
ww = window["bounds"]["width"]
|
||||||
|
wh = window["bounds"]["height"]
|
||||||
|
window_rect = ((wx, wy), (ww, wh))
|
||||||
|
for display in displays:
|
||||||
|
display_rect = ((display["originX"], display["originY"]), (display["width"], display["height"]))
|
||||||
|
intersection = CGRectIntersection(window_rect, display_rect)
|
||||||
|
if intersection.size.width > 0 and intersection.size.height > 0:
|
||||||
|
display_ids.add(int(display["displayId"]))
|
||||||
|
result.append({"bundleId": bundle_id, "displayIds": sorted(display_ids)})
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def open_app(bundle_id: str) -> None:
|
||||||
|
url = bundle_id_to_app(bundle_id)
|
||||||
|
if not url:
|
||||||
|
raise RuntimeError(f"App not found for bundle identifier: {bundle_id}")
|
||||||
|
ok, err = NSWorkspace.sharedWorkspace().launchApplicationAtURL_options_configuration_error_(url, 0, {}, None)
|
||||||
|
if not ok:
|
||||||
|
raise RuntimeError(str(err) if err else f"Failed to open app {bundle_id}")
|
||||||
|
|
||||||
|
|
||||||
|
def read_clipboard() -> str:
|
||||||
|
pb = NSPasteboard.generalPasteboard()
|
||||||
|
value = pb.stringForType_(NSPasteboardTypeString)
|
||||||
|
return "" if value is None else str(value)
|
||||||
|
|
||||||
|
|
||||||
|
def write_clipboard(text: str) -> None:
|
||||||
|
pb = NSPasteboard.generalPasteboard()
|
||||||
|
pb.clearContents()
|
||||||
|
pb.setString_forType_(text, NSPasteboardTypeString)
|
||||||
|
|
||||||
|
|
||||||
|
def check_permissions() -> dict[str, bool]:
|
||||||
|
accessibility = True
|
||||||
|
try:
|
||||||
|
run_osascript('tell application "System Events" to get name of first process')
|
||||||
|
except Exception:
|
||||||
|
accessibility = False
|
||||||
|
screen_recording = True
|
||||||
|
try:
|
||||||
|
capture_display(None)
|
||||||
|
except Exception:
|
||||||
|
screen_recording = False
|
||||||
|
return {
|
||||||
|
"accessibility": accessibility,
|
||||||
|
"screenRecording": screen_recording,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def click(x: int, y: int, button: str, count: int, modifiers: list[str] | None) -> None:
|
||||||
|
pyautogui.moveTo(x, y)
|
||||||
|
if modifiers:
|
||||||
|
normalized = [normalize_key(m) for m in modifiers]
|
||||||
|
for key in normalized:
|
||||||
|
pyautogui.keyDown(key)
|
||||||
|
try:
|
||||||
|
pyautogui.click(x=x, y=y, button=button, clicks=count, interval=0.08)
|
||||||
|
finally:
|
||||||
|
for key in reversed(normalized):
|
||||||
|
pyautogui.keyUp(key)
|
||||||
|
else:
|
||||||
|
pyautogui.click(x=x, y=y, button=button, clicks=count, interval=0.08)
|
||||||
|
|
||||||
|
|
||||||
|
def scroll(x: int, y: int, delta_x: int, delta_y: int) -> None:
|
||||||
|
pyautogui.moveTo(x, y)
|
||||||
|
if delta_y:
|
||||||
|
pyautogui.scroll(int(delta_y), x=x, y=y)
|
||||||
|
if delta_x:
|
||||||
|
pyautogui.hscroll(int(delta_x), x=x, y=y)
|
||||||
|
|
||||||
|
|
||||||
|
def key_action(sequence: str, repeat: int = 1) -> None:
|
||||||
|
parts = [normalize_key(part) for part in sequence.split("+") if part.strip()]
|
||||||
|
for _ in range(max(1, repeat)):
|
||||||
|
if len(parts) == 1:
|
||||||
|
pyautogui.press(parts[0])
|
||||||
|
else:
|
||||||
|
pyautogui.hotkey(*parts, interval=0.02)
|
||||||
|
time.sleep(0.01)
|
||||||
|
|
||||||
|
|
||||||
|
def hold_keys(keys: list[str], duration_ms: int) -> None:
|
||||||
|
normalized = [normalize_key(k) for k in keys]
|
||||||
|
for key in normalized:
|
||||||
|
pyautogui.keyDown(key)
|
||||||
|
try:
|
||||||
|
time.sleep(max(duration_ms, 0) / 1000)
|
||||||
|
finally:
|
||||||
|
for key in reversed(normalized):
|
||||||
|
pyautogui.keyUp(key)
|
||||||
|
|
||||||
|
|
||||||
|
def type_text(text: str) -> None:
|
||||||
|
pyautogui.write(text, interval=0.008)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("command")
|
||||||
|
parser.add_argument("--payload", default="{}")
|
||||||
|
args = parser.parse_args()
|
||||||
|
payload = json.loads(args.payload)
|
||||||
|
|
||||||
|
try:
|
||||||
|
command = args.command
|
||||||
|
if command == "check_permissions":
|
||||||
|
perms = check_permissions()
|
||||||
|
json_output({"ok": True, "result": perms})
|
||||||
|
return 0
|
||||||
|
if command == "list_displays":
|
||||||
|
json_output({"ok": True, "result": get_displays()})
|
||||||
|
return 0
|
||||||
|
if command == "get_display_size":
|
||||||
|
json_output({"ok": True, "result": choose_display(payload.get("displayId"))})
|
||||||
|
return 0
|
||||||
|
if command == "screenshot":
|
||||||
|
resize = None
|
||||||
|
if payload.get("targetWidth") and payload.get("targetHeight"):
|
||||||
|
resize = (int(payload["targetWidth"]), int(payload["targetHeight"]))
|
||||||
|
result = capture_display(payload.get("displayId"), resize)
|
||||||
|
json_output({"ok": True, "result": result})
|
||||||
|
return 0
|
||||||
|
if command == "resolve_prepare_capture":
|
||||||
|
resize = None
|
||||||
|
if payload.get("targetWidth") and payload.get("targetHeight"):
|
||||||
|
resize = (int(payload["targetWidth"]), int(payload["targetHeight"]))
|
||||||
|
result = capture_display(payload.get("preferredDisplayId"), resize)
|
||||||
|
result["hidden"] = []
|
||||||
|
result["resolvedDisplayId"] = result["displayId"]
|
||||||
|
json_output({"ok": True, "result": result})
|
||||||
|
return 0
|
||||||
|
if command == "zoom":
|
||||||
|
resize = None
|
||||||
|
if payload.get("targetWidth") and payload.get("targetHeight"):
|
||||||
|
resize = (int(payload["targetWidth"]), int(payload["targetHeight"]))
|
||||||
|
region = {
|
||||||
|
"left": int(payload["x"]),
|
||||||
|
"top": int(payload["y"]),
|
||||||
|
"width": int(payload["width"]),
|
||||||
|
"height": int(payload["height"]),
|
||||||
|
}
|
||||||
|
json_output({"ok": True, "result": capture_region(region, resize)})
|
||||||
|
return 0
|
||||||
|
if command == "prepare_for_action":
|
||||||
|
json_output({"ok": True, "result": []})
|
||||||
|
return 0
|
||||||
|
if command == "preview_hide_set":
|
||||||
|
json_output({"ok": True, "result": []})
|
||||||
|
return 0
|
||||||
|
if command == "find_window_displays":
|
||||||
|
json_output({"ok": True, "result": find_window_displays(list(payload.get("bundleIds") or []))})
|
||||||
|
return 0
|
||||||
|
if command == "key":
|
||||||
|
key_action(str(payload["keySequence"]), int(payload.get("repeat") or 1))
|
||||||
|
json_output({"ok": True, "result": True})
|
||||||
|
return 0
|
||||||
|
if command == "hold_key":
|
||||||
|
hold_keys(list(payload.get("keyNames") or []), int(payload.get("durationMs") or 0))
|
||||||
|
json_output({"ok": True, "result": True})
|
||||||
|
return 0
|
||||||
|
if command == "type":
|
||||||
|
type_text(str(payload.get("text") or ""))
|
||||||
|
json_output({"ok": True, "result": True})
|
||||||
|
return 0
|
||||||
|
if command == "click":
|
||||||
|
click(int(payload["x"]), int(payload["y"]), str(payload.get("button") or "left"), int(payload.get("count") or 1), payload.get("modifiers"))
|
||||||
|
json_output({"ok": True, "result": True})
|
||||||
|
return 0
|
||||||
|
if command == "drag":
|
||||||
|
from_point = payload.get("from")
|
||||||
|
if from_point:
|
||||||
|
pyautogui.moveTo(int(from_point["x"]), int(from_point["y"]))
|
||||||
|
pyautogui.dragTo(int(payload["to"]["x"]), int(payload["to"]["y"]), duration=0.2, button="left")
|
||||||
|
json_output({"ok": True, "result": True})
|
||||||
|
return 0
|
||||||
|
if command == "move_mouse":
|
||||||
|
pyautogui.moveTo(int(payload["x"]), int(payload["y"]))
|
||||||
|
json_output({"ok": True, "result": True})
|
||||||
|
return 0
|
||||||
|
if command == "scroll":
|
||||||
|
scroll(int(payload["x"]), int(payload["y"]), int(payload.get("deltaX") or 0), int(payload.get("deltaY") or 0))
|
||||||
|
json_output({"ok": True, "result": True})
|
||||||
|
return 0
|
||||||
|
if command == "mouse_down":
|
||||||
|
pyautogui.mouseDown(button="left")
|
||||||
|
json_output({"ok": True, "result": True})
|
||||||
|
return 0
|
||||||
|
if command == "mouse_up":
|
||||||
|
pyautogui.mouseUp(button="left")
|
||||||
|
json_output({"ok": True, "result": True})
|
||||||
|
return 0
|
||||||
|
if command == "cursor_position":
|
||||||
|
x, y = pyautogui.position()
|
||||||
|
json_output({"ok": True, "result": {"x": int(x), "y": int(y)}})
|
||||||
|
return 0
|
||||||
|
if command == "frontmost_app":
|
||||||
|
json_output({"ok": True, "result": frontmost_app()})
|
||||||
|
return 0
|
||||||
|
if command == "app_under_point":
|
||||||
|
json_output({"ok": True, "result": app_under_point(int(payload["x"]), int(payload["y"]))})
|
||||||
|
return 0
|
||||||
|
if command == "list_installed_apps":
|
||||||
|
json_output({"ok": True, "result": installed_apps()})
|
||||||
|
return 0
|
||||||
|
if command == "list_running_apps":
|
||||||
|
json_output({"ok": True, "result": running_apps()})
|
||||||
|
return 0
|
||||||
|
if command == "open_app":
|
||||||
|
open_app(str(payload["bundleId"]))
|
||||||
|
json_output({"ok": True, "result": True})
|
||||||
|
return 0
|
||||||
|
if command == "read_clipboard":
|
||||||
|
json_output({"ok": True, "result": read_clipboard()})
|
||||||
|
return 0
|
||||||
|
if command == "write_clipboard":
|
||||||
|
write_clipboard(str(payload.get("text") or ""))
|
||||||
|
json_output({"ok": True, "result": True})
|
||||||
|
return 0
|
||||||
|
error_output(f"Unknown command: {command}", code="bad_command")
|
||||||
|
return 2
|
||||||
|
except Exception as exc:
|
||||||
|
error_output(str(exc))
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
6
runtime/requirements.txt
Normal file
6
runtime/requirements.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
mss>=10.1.0
|
||||||
|
Pillow>=11.3.0
|
||||||
|
pyautogui>=0.9.54
|
||||||
|
pyobjc-core>=11.1
|
||||||
|
pyobjc-framework-Cocoa>=11.1
|
||||||
|
pyobjc-framework-Quartz>=11.1
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { c as _c } from "react/compiler-runtime";
|
import { c as _c } from "react/compiler-runtime";
|
||||||
import { getSentinelCategory } from '@ant/computer-use-mcp/sentinelApps';
|
import { getSentinelCategory } from '../../../vendor/computer-use-mcp/sentinelApps.js';
|
||||||
import type { CuPermissionRequest, CuPermissionResponse } from '@ant/computer-use-mcp/types';
|
import type { CuPermissionRequest, CuPermissionResponse } from '../../../vendor/computer-use-mcp/types.js';
|
||||||
import { DEFAULT_GRANT_FLAGS } from '@ant/computer-use-mcp/types';
|
import { DEFAULT_GRANT_FLAGS } from '../../../vendor/computer-use-mcp/types.js';
|
||||||
import figures from 'figures';
|
import figures from 'figures';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
|
|||||||
@ -83,7 +83,7 @@ async function main(): Promise<void> {
|
|||||||
} = await import('../utils/claudeInChrome/chromeNativeHost.js');
|
} = await import('../utils/claudeInChrome/chromeNativeHost.js');
|
||||||
await runChromeNativeHost();
|
await runChromeNativeHost();
|
||||||
return;
|
return;
|
||||||
} else if (feature('CHICAGO_MCP') && process.argv[2] === '--computer-use-mcp') {
|
} else if (process.argv[2] === '--computer-use-mcp') {
|
||||||
profileCheckpoint('cli_computer_use_mcp_path');
|
profileCheckpoint('cli_computer_use_mcp_path');
|
||||||
const {
|
const {
|
||||||
runComputerUseMcpServer
|
runComputerUseMcpServer
|
||||||
|
|||||||
@ -1474,7 +1474,7 @@ async function run(): Promise<CommanderCommand> {
|
|||||||
let reservedNameError: string | null = null;
|
let reservedNameError: string | null = null;
|
||||||
if (nonSdkConfigNames.some(isClaudeInChromeMCPServer)) {
|
if (nonSdkConfigNames.some(isClaudeInChromeMCPServer)) {
|
||||||
reservedNameError = `Invalid MCP configuration: "${CLAUDE_IN_CHROME_MCP_SERVER_NAME}" is a reserved MCP name.`;
|
reservedNameError = `Invalid MCP configuration: "${CLAUDE_IN_CHROME_MCP_SERVER_NAME}" is a reserved MCP name.`;
|
||||||
} else if (feature('CHICAGO_MCP')) {
|
} else {
|
||||||
const {
|
const {
|
||||||
isComputerUseMCPServer,
|
isComputerUseMCPServer,
|
||||||
COMPUTER_USE_MCP_SERVER_NAME
|
COMPUTER_USE_MCP_SERVER_NAME
|
||||||
@ -1605,7 +1605,7 @@ async function run(): Promise<CommanderCommand> {
|
|||||||
// `type: 'stdio'`. An enterprise-config ant with the GB gate on would
|
// `type: 'stdio'`. An enterprise-config ant with the GB gate on would
|
||||||
// otherwise process.exit(1). Chrome has the same latent issue but has
|
// otherwise process.exit(1). Chrome has the same latent issue but has
|
||||||
// shipped without incident; chicago places itself correctly.
|
// shipped without incident; chicago places itself correctly.
|
||||||
if (feature('CHICAGO_MCP') && getPlatform() === 'macos' && !getIsNonInteractiveSession()) {
|
if (getPlatform() === 'macos' && !getIsNonInteractiveSession()) {
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
getChicagoEnabled
|
getChicagoEnabled
|
||||||
|
|||||||
@ -1030,7 +1030,7 @@ async function* queryLoop(
|
|||||||
// chicago MCP: auto-unhide + lock release on interrupt. Same cleanup
|
// chicago MCP: auto-unhide + lock release on interrupt. Same cleanup
|
||||||
// as the natural turn-end path in stopHooks.ts. Main thread only —
|
// as the natural turn-end path in stopHooks.ts. Main thread only —
|
||||||
// see stopHooks.ts for the subagent-releasing-main's-lock rationale.
|
// see stopHooks.ts for the subagent-releasing-main's-lock rationale.
|
||||||
if (feature('CHICAGO_MCP') && !toolUseContext.agentId) {
|
if (!toolUseContext.agentId) {
|
||||||
try {
|
try {
|
||||||
const { cleanupComputerUseAfterTurn } = await import(
|
const { cleanupComputerUseAfterTurn } = await import(
|
||||||
'./utils/computerUse/cleanup.js'
|
'./utils/computerUse/cleanup.js'
|
||||||
@ -1486,7 +1486,7 @@ async function* queryLoop(
|
|||||||
// chicago MCP: auto-unhide + lock release when aborted mid-tool-call.
|
// chicago MCP: auto-unhide + lock release when aborted mid-tool-call.
|
||||||
// This is the most likely Ctrl+C path for CU (e.g. slow screenshot).
|
// This is the most likely Ctrl+C path for CU (e.g. slow screenshot).
|
||||||
// Main thread only — see stopHooks.ts for the subagent rationale.
|
// Main thread only — see stopHooks.ts for the subagent rationale.
|
||||||
if (feature('CHICAGO_MCP') && !toolUseContext.agentId) {
|
if (!toolUseContext.agentId) {
|
||||||
try {
|
try {
|
||||||
const { cleanupComputerUseAfterTurn } = await import(
|
const { cleanupComputerUseAfterTurn } = await import(
|
||||||
'./utils/computerUse/cleanup.js'
|
'./utils/computerUse/cleanup.js'
|
||||||
|
|||||||
@ -161,7 +161,7 @@ export async function* handleStopHooks(
|
|||||||
// so a subagent's stopHooks releasing it leaves the main thread's cleanup
|
// so a subagent's stopHooks releasing it leaves the main thread's cleanup
|
||||||
// seeing isLockHeldLocally()===false → no exit notification, and unhides
|
// seeing isLockHeldLocally()===false → no exit notification, and unhides
|
||||||
// mid-turn. Subagents don't start CU sessions so this is a pure skip.
|
// mid-turn. Subagents don't start CU sessions so this is a pure skip.
|
||||||
if (feature('CHICAGO_MCP') && !toolUseContext.agentId) {
|
if (!toolUseContext.agentId) {
|
||||||
try {
|
try {
|
||||||
const { cleanupComputerUseAfterTurn } = await import(
|
const { cleanupComputerUseAfterTurn } = await import(
|
||||||
'../utils/computerUse/cleanup.js'
|
'../utils/computerUse/cleanup.js'
|
||||||
|
|||||||
@ -126,15 +126,11 @@ export function isAnalyticsToolDetailsLoggingEnabled(
|
|||||||
* a user-configured 'computer-use' is possible in builds without the feature.
|
* a user-configured 'computer-use' is possible in builds without the feature.
|
||||||
*/
|
*/
|
||||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
const BUILTIN_MCP_SERVER_NAMES: ReadonlySet<string> = new Set(
|
const BUILTIN_MCP_SERVER_NAMES: ReadonlySet<string> = new Set([
|
||||||
feature('CHICAGO_MCP')
|
(
|
||||||
? [
|
require('../../utils/computerUse/common.js') as typeof import('../../utils/computerUse/common.js')
|
||||||
(
|
).COMPUTER_USE_MCP_SERVER_NAME,
|
||||||
require('../../utils/computerUse/common.js') as typeof import('../../utils/computerUse/common.js')
|
])
|
||||||
).COMPUTER_USE_MCP_SERVER_NAME,
|
|
||||||
]
|
|
||||||
: [],
|
|
||||||
)
|
|
||||||
/* eslint-enable @typescript-eslint/no-require-imports */
|
/* eslint-enable @typescript-eslint/no-require-imports */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -238,15 +238,12 @@ const claudeInChromeToolRendering =
|
|||||||
// Lazy: wrapper.tsx → hostAdapter.ts → executor.ts pulls both native modules
|
// Lazy: wrapper.tsx → hostAdapter.ts → executor.ts pulls both native modules
|
||||||
// (@ant/computer-use-input + @ant/computer-use-swift). Runtime-gated by
|
// (@ant/computer-use-input + @ant/computer-use-swift). Runtime-gated by
|
||||||
// GrowthBook tengu_malort_pedway (see gates.ts).
|
// GrowthBook tengu_malort_pedway (see gates.ts).
|
||||||
const computerUseWrapper = feature('CHICAGO_MCP')
|
const computerUseWrapper =
|
||||||
? (): typeof import('../../utils/computerUse/wrapper.js') =>
|
(): typeof import('../../utils/computerUse/wrapper.js') =>
|
||||||
require('../../utils/computerUse/wrapper.js')
|
require('../../utils/computerUse/wrapper.js')
|
||||||
: undefined
|
const isComputerUseMCPServer = (
|
||||||
const isComputerUseMCPServer = feature('CHICAGO_MCP')
|
require('../../utils/computerUse/common.js') as typeof import('../../utils/computerUse/common.js')
|
||||||
? (
|
).isComputerUseMCPServer
|
||||||
require('../../utils/computerUse/common.js') as typeof import('../../utils/computerUse/common.js')
|
|
||||||
).isComputerUseMCPServer
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
import { mkdir, readFile, unlink, writeFile } from 'fs/promises'
|
import { mkdir, readFile, unlink, writeFile } from 'fs/promises'
|
||||||
import { dirname, join } from 'path'
|
import { dirname, join } from 'path'
|
||||||
@ -923,9 +920,8 @@ export const connectToServer = memoize(
|
|||||||
transport = clientTransport
|
transport = clientTransport
|
||||||
logMCPDebug(name, `In-process Chrome MCP server started`)
|
logMCPDebug(name, `In-process Chrome MCP server started`)
|
||||||
} else if (
|
} else if (
|
||||||
feature('CHICAGO_MCP') &&
|
|
||||||
(serverRef.type === 'stdio' || !serverRef.type) &&
|
(serverRef.type === 'stdio' || !serverRef.type) &&
|
||||||
isComputerUseMCPServer!(name)
|
isComputerUseMCPServer(name)
|
||||||
) {
|
) {
|
||||||
// Run the Computer Use MCP server in-process — same rationale as
|
// Run the Computer Use MCP server in-process — same rationale as
|
||||||
// Chrome above. The package's CallTool handler is a stub; real
|
// Chrome above. The package's CallTool handler is a stub; real
|
||||||
@ -1980,10 +1976,9 @@ export const fetchToolsForClient = memoizeWithLRU(
|
|||||||
tool.name,
|
tool.name,
|
||||||
)
|
)
|
||||||
: {}),
|
: {}),
|
||||||
...(feature('CHICAGO_MCP') &&
|
...((client.config.type === 'stdio' || !client.config.type) &&
|
||||||
(client.config.type === 'stdio' || !client.config.type) &&
|
isComputerUseMCPServer(client.name)
|
||||||
isComputerUseMCPServer!(client.name)
|
? computerUseWrapper().getComputerUseMCPToolOverrides(tool.name)
|
||||||
? computerUseWrapper!().getComputerUseMCPToolOverrides(tool.name)
|
|
||||||
: {}),
|
: {}),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -638,13 +638,11 @@ export async function addMcpConfig(
|
|||||||
throw new Error(`Cannot add MCP server "${name}": this name is reserved.`)
|
throw new Error(`Cannot add MCP server "${name}": this name is reserved.`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (feature('CHICAGO_MCP')) {
|
const { isComputerUseMCPServer } = await import(
|
||||||
const { isComputerUseMCPServer } = await import(
|
'../../utils/computerUse/common.js'
|
||||||
'../../utils/computerUse/common.js'
|
)
|
||||||
)
|
if (isComputerUseMCPServer(name)) {
|
||||||
if (isComputerUseMCPServer(name)) {
|
throw new Error(`Cannot add MCP server "${name}": this name is reserved.`)
|
||||||
throw new Error(`Cannot add MCP server "${name}": this name is reserved.`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Block adding servers when enterprise MCP config exists (it has exclusive control)
|
// Block adding servers when enterprise MCP config exists (it has exclusive control)
|
||||||
@ -1509,15 +1507,13 @@ export function areMcpConfigsAllowedWithEnterpriseMcpConfig(
|
|||||||
* enabledMcpServers. Shows up in /mcp as disabled until the user enables it.
|
* enabledMcpServers. Shows up in /mcp as disabled until the user enables it.
|
||||||
*/
|
*/
|
||||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||||
const DEFAULT_DISABLED_BUILTIN = feature('CHICAGO_MCP')
|
const DEFAULT_DISABLED_BUILTIN = (
|
||||||
? (
|
require('../../utils/computerUse/common.js') as typeof import('../../utils/computerUse/common.js')
|
||||||
require('../../utils/computerUse/common.js') as typeof import('../../utils/computerUse/common.js')
|
).COMPUTER_USE_MCP_SERVER_NAME
|
||||||
).COMPUTER_USE_MCP_SERVER_NAME
|
|
||||||
: null
|
|
||||||
/* eslint-enable @typescript-eslint/no-require-imports */
|
/* eslint-enable @typescript-eslint/no-require-imports */
|
||||||
|
|
||||||
function isDefaultDisabledBuiltin(name: string): boolean {
|
function isDefaultDisabledBuiltin(_name: string): boolean {
|
||||||
return DEFAULT_DISABLED_BUILTIN !== null && name === DEFAULT_DISABLED_BUILTIN
|
return false // Computer Use 默认启用,无需用户手动 enable
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,79 +1,11 @@
|
|||||||
import { logForDebugging } from '../debug.js'
|
|
||||||
import { withResolvers } from '../withResolvers.js'
|
|
||||||
import { requireComputerUseSwift } from './swiftLoader.js'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shared CFRunLoop pump. Swift's four `@MainActor` async methods
|
* No-op replacement. The Python bridge is a synchronous subprocess call —
|
||||||
* (captureExcluding, captureRegion, apps.listInstalled, resolvePrepareCapture)
|
* there is no CFRunLoop to pump. All former callers can just await their
|
||||||
* and `@ant/computer-use-input`'s key()/keys() all dispatch to
|
* promises directly.
|
||||||
* DispatchQueue.main. Under libuv (Node/bun) that queue never drains — the
|
|
||||||
* promises hang. Electron drains it via CFRunLoop so Cowork doesn't need this.
|
|
||||||
*
|
|
||||||
* One refcounted setInterval calls `_drainMainRunLoop` (RunLoop.main.run)
|
|
||||||
* every 1ms while any main-queue-dependent call is pending. Multiple
|
|
||||||
* concurrent drainRunLoop() calls share the single pump via retain/release.
|
|
||||||
*/
|
|
||||||
|
|
||||||
let pump: ReturnType<typeof setInterval> | undefined
|
|
||||||
let pending = 0
|
|
||||||
|
|
||||||
function drainTick(cu: ReturnType<typeof requireComputerUseSwift>): void {
|
|
||||||
cu._drainMainRunLoop()
|
|
||||||
}
|
|
||||||
|
|
||||||
function retain(): void {
|
|
||||||
pending++
|
|
||||||
if (pump === undefined) {
|
|
||||||
pump = setInterval(drainTick, 1, requireComputerUseSwift())
|
|
||||||
logForDebugging('[drainRunLoop] pump started', { level: 'verbose' })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function release(): void {
|
|
||||||
pending--
|
|
||||||
if (pending <= 0 && pump !== undefined) {
|
|
||||||
clearInterval(pump)
|
|
||||||
pump = undefined
|
|
||||||
logForDebugging('[drainRunLoop] pump stopped', { level: 'verbose' })
|
|
||||||
pending = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const TIMEOUT_MS = 30_000
|
|
||||||
|
|
||||||
function timeoutReject(reject: (e: Error) => void): void {
|
|
||||||
reject(new Error(`computer-use native call exceeded ${TIMEOUT_MS}ms`))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hold a pump reference for the lifetime of a long-lived registration
|
|
||||||
* (e.g. the CGEventTap Escape handler). Unlike `drainRunLoop(fn)` this has
|
|
||||||
* no timeout — the caller is responsible for calling `releasePump()`. Same
|
|
||||||
* refcount as drainRunLoop calls, so nesting is safe.
|
|
||||||
*/
|
|
||||||
export const retainPump = retain
|
|
||||||
export const releasePump = release
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Await `fn()` with the shared drain pump running. Safe to nest — multiple
|
|
||||||
* concurrent drainRunLoop() calls share one setInterval.
|
|
||||||
*/
|
*/
|
||||||
export async function drainRunLoop<T>(fn: () => Promise<T>): Promise<T> {
|
export async function drainRunLoop<T>(fn: () => Promise<T>): Promise<T> {
|
||||||
retain()
|
return fn()
|
||||||
let timer: ReturnType<typeof setTimeout> | undefined
|
|
||||||
try {
|
|
||||||
// If the timeout wins the race, fn()'s promise is orphaned — a late
|
|
||||||
// rejection from the native layer would become an unhandledRejection.
|
|
||||||
// Attaching a no-op catch swallows it; the timeout error is what surfaces.
|
|
||||||
// fn() sits inside try so a synchronous throw (e.g. NAPI argument
|
|
||||||
// validation) still reaches release() — otherwise the pump leaks.
|
|
||||||
const work = fn()
|
|
||||||
work.catch(() => {})
|
|
||||||
const timeout = withResolvers<never>()
|
|
||||||
timer = setTimeout(timeoutReject, TIMEOUT_MS, timeout.reject)
|
|
||||||
return await Promise.race([work, timeout.promise])
|
|
||||||
} finally {
|
|
||||||
clearTimeout(timer)
|
|
||||||
release()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const retainPump = () => {}
|
||||||
|
export const releasePump = () => {}
|
||||||
|
|||||||
@ -1,54 +1,12 @@
|
|||||||
import { logForDebugging } from '../debug.js'
|
|
||||||
import { releasePump, retainPump } from './drainRunLoop.js'
|
|
||||||
import { requireComputerUseSwift } from './swiftLoader.js'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Global Escape → abort. Mirrors Cowork's `escAbort.ts` but without Electron:
|
* No-op replacement. The Python bridge does not support global Escape hotkey
|
||||||
* CGEventTap via `@ant/computer-use-swift`. While registered, Escape is
|
* via CGEventTap. The original implementation required native Swift module
|
||||||
* consumed system-wide (PI defense — a prompt-injected action can't dismiss
|
* for CGEventTap-based system-wide key interception.
|
||||||
* a dialog with Escape).
|
|
||||||
*
|
|
||||||
* Lifecycle: register on fresh lock acquire (`wrapper.tsx` `acquireCuLock`),
|
|
||||||
* unregister on lock release (`cleanup.ts`). The tap's CFRunLoopSource sits
|
|
||||||
* in .defaultMode on CFRunLoopGetMain(), so we hold a drainRunLoop pump
|
|
||||||
* retain for the registration's lifetime — same refcounted setInterval as
|
|
||||||
* the `@MainActor` methods.
|
|
||||||
*
|
|
||||||
* `notifyExpectedEscape()` punches a hole for model-synthesized Escapes: the
|
|
||||||
* executor's `key("escape")` calls it before posting the CGEvent. Swift
|
|
||||||
* schedules a 100ms decay so a CGEvent that never reaches the tap callback
|
|
||||||
* doesn't eat the next user ESC.
|
|
||||||
*/
|
*/
|
||||||
|
export function registerEscHotkey(_onEscape: () => void): boolean {
|
||||||
let registered = false
|
return false
|
||||||
|
|
||||||
export function registerEscHotkey(onEscape: () => void): boolean {
|
|
||||||
if (registered) return true
|
|
||||||
const cu = requireComputerUseSwift()
|
|
||||||
if (!cu.hotkey.registerEscape(onEscape)) {
|
|
||||||
// CGEvent.tapCreate failed — typically missing Accessibility permission.
|
|
||||||
// CU still works, just without ESC abort. Mirrors Cowork's escAbort.ts:81.
|
|
||||||
logForDebugging('[cu-esc] registerEscape returned false', { level: 'warn' })
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
retainPump()
|
|
||||||
registered = true
|
|
||||||
logForDebugging('[cu-esc] registered')
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unregisterEscHotkey(): void {
|
export function unregisterEscHotkey(): void {}
|
||||||
if (!registered) return
|
|
||||||
try {
|
|
||||||
requireComputerUseSwift().hotkey.unregister()
|
|
||||||
} finally {
|
|
||||||
releasePump()
|
|
||||||
registered = false
|
|
||||||
logForDebugging('[cu-esc] unregistered')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function notifyExpectedEscape(): void {
|
export function notifyExpectedEscape(): void {}
|
||||||
if (!registered) return
|
|
||||||
requireComputerUseSwift().hotkey.notifyExpectedEscape()
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,30 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* CLI `ComputerExecutor` implementation. Wraps two native modules:
|
* CLI `ComputerExecutor` implementation — Python bridge variant.
|
||||||
* - `@ant/computer-use-input` (Rust/enigo) — mouse, keyboard, frontmost app
|
|
||||||
* - `@ant/computer-use-swift` — SCContentFilter screenshots, NSWorkspace apps, TCC
|
|
||||||
*
|
*
|
||||||
* Contract: `packages/desktop/computer-use-mcp/src/executor.ts` in the apps
|
* Replaces the native Swift/Rust modules with a Python subprocess bridge
|
||||||
* repo. The reference impl is Cowork's `apps/desktop/src/main/nest-only/
|
* (pyautogui + mss + pyobjc). See `pythonBridge.ts` and `runtime/mac_helper.py`.
|
||||||
* computer-use/executor.ts` — see notable deviations under "CLI deltas" below.
|
|
||||||
*
|
|
||||||
* ── CLI deltas from Cowork ─────────────────────────────────────────────────
|
|
||||||
*
|
|
||||||
* No `withClickThrough`. Cowork wraps every mouse op in
|
|
||||||
* `BrowserWindow.setIgnoreMouseEvents(true)` so clicks fall through the
|
|
||||||
* overlay. We're a terminal — no window — so the click-through bracket is
|
|
||||||
* a no-op. The sentinel `CLI_HOST_BUNDLE_ID` never matches frontmost.
|
|
||||||
*
|
|
||||||
* Terminal as surrogate host. `getTerminalBundleId()` detects the emulator
|
|
||||||
* we're running inside. It's passed as `hostBundleId` to `prepareDisplay`/
|
|
||||||
* `resolvePrepareCapture` so the Swift side exempts it from hide AND skips
|
|
||||||
* it in the activate z-order walk (so the terminal being frontmost doesn't
|
|
||||||
* eat clicks meant for the target app). Also stripped from `allowedBundleIds`
|
|
||||||
* via `withoutTerminal()` so screenshots don't capture it (Swift 0.2.1's
|
|
||||||
* captureExcluding takes an allow-list despite the name — apps#30355).
|
|
||||||
* `capabilities.hostBundleId` stays as the sentinel — the package's
|
|
||||||
* frontmost gate uses that, and the terminal being frontmost is fine.
|
|
||||||
*
|
|
||||||
* Clipboard via `pbcopy`/`pbpaste`. No Electron `clipboard` module.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@ -35,28 +13,22 @@ import type {
|
|||||||
ResolvePrepareCaptureResult,
|
ResolvePrepareCaptureResult,
|
||||||
RunningApp,
|
RunningApp,
|
||||||
ScreenshotResult,
|
ScreenshotResult,
|
||||||
} from '@ant/computer-use-mcp'
|
} from '../../vendor/computer-use-mcp/index.js'
|
||||||
|
import { API_RESIZE_PARAMS, targetImageSize } from '../../vendor/computer-use-mcp/index.js'
|
||||||
import { API_RESIZE_PARAMS, targetImageSize } from '@ant/computer-use-mcp'
|
|
||||||
import { logForDebugging } from '../debug.js'
|
|
||||||
import { errorMessage } from '../errors.js'
|
|
||||||
import { execFileNoThrow } from '../execFileNoThrow.js'
|
import { execFileNoThrow } from '../execFileNoThrow.js'
|
||||||
import { sleep } from '../sleep.js'
|
import { sleep } from '../sleep.js'
|
||||||
import {
|
import { CLI_CU_CAPABILITIES, CLI_HOST_BUNDLE_ID } from './common.js'
|
||||||
CLI_CU_CAPABILITIES,
|
import { callPythonHelper } from './pythonBridge.js'
|
||||||
CLI_HOST_BUNDLE_ID,
|
|
||||||
getTerminalBundleId,
|
|
||||||
} from './common.js'
|
|
||||||
import { drainRunLoop } from './drainRunLoop.js'
|
|
||||||
import { notifyExpectedEscape } from './escHotkey.js'
|
|
||||||
import { requireComputerUseInput } from './inputLoader.js'
|
|
||||||
import { requireComputerUseSwift } from './swiftLoader.js'
|
|
||||||
|
|
||||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
const SCREENSHOT_JPEG_QUALITY = 0.75
|
const SCREENSHOT_JPEG_QUALITY = 0.75
|
||||||
|
const MOVE_SETTLE_MS = 50
|
||||||
|
|
||||||
|
type PythonDisplayGeometry = DisplayGeometry
|
||||||
|
|
||||||
|
type PythonResolvePrepareCaptureResult = ResolvePrepareCaptureResult & {
|
||||||
|
displayId?: number
|
||||||
|
}
|
||||||
|
|
||||||
/** Logical → physical → API target dims. See `targetImageSize` + COORDINATES.md. */
|
|
||||||
function computeTargetDims(
|
function computeTargetDims(
|
||||||
logicalW: number,
|
logicalW: number,
|
||||||
logicalH: number,
|
logicalH: number,
|
||||||
@ -67,592 +39,192 @@ function computeTargetDims(
|
|||||||
return targetImageSize(physW, physH, API_RESIZE_PARAMS)
|
return targetImageSize(physW, physH, API_RESIZE_PARAMS)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readClipboardViaPbpaste(): Promise<string> {
|
function normalizeDisplayGeometry(display: PythonDisplayGeometry): DisplayGeometry {
|
||||||
const { stdout, code } = await execFileNoThrow('pbpaste', [], {
|
return {
|
||||||
useCwd: false,
|
...display,
|
||||||
})
|
displayId: display.displayId ?? display.id,
|
||||||
if (code !== 0) {
|
label: display.label ?? display.name,
|
||||||
throw new Error(`pbpaste exited with code ${code}`)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readClipboardViaPbpaste(): Promise<string> {
|
||||||
|
const { stdout, code } = await execFileNoThrow('pbpaste', [], { useCwd: false })
|
||||||
|
if (code !== 0) throw new Error(`pbpaste exited with code ${code}`)
|
||||||
return stdout
|
return stdout
|
||||||
}
|
}
|
||||||
|
|
||||||
async function writeClipboardViaPbcopy(text: string): Promise<void> {
|
async function writeClipboardViaPbcopy(text: string): Promise<void> {
|
||||||
const { code } = await execFileNoThrow('pbcopy', [], {
|
const { code } = await execFileNoThrow('pbcopy', [], { input: text, useCwd: false })
|
||||||
input: text,
|
if (code !== 0) throw new Error(`pbcopy exited with code ${code}`)
|
||||||
useCwd: false,
|
|
||||||
})
|
|
||||||
if (code !== 0) {
|
|
||||||
throw new Error(`pbcopy exited with code ${code}`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Input = ReturnType<typeof requireComputerUseInput>
|
async function typeViaClipboard(text: string): Promise<void> {
|
||||||
|
|
||||||
/**
|
|
||||||
* Single-element key sequence matching "escape" or "esc" (case-insensitive).
|
|
||||||
* Used to hole-punch the CGEventTap abort for model-synthesized Escape — enigo
|
|
||||||
* accepts both spellings, so the tap must too.
|
|
||||||
*/
|
|
||||||
function isBareEscape(parts: readonly string[]): boolean {
|
|
||||||
if (parts.length !== 1) return false
|
|
||||||
const lower = parts[0]!.toLowerCase()
|
|
||||||
return lower === 'escape' || lower === 'esc'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instant move, then 50ms — an input→HID→AppKit→NSEvent round-trip before the
|
|
||||||
* caller reads `NSEvent.mouseLocation` or dispatches a click. Used for click,
|
|
||||||
* scroll, and drag-from; `animatedMove` is reserved for drag-to only. The
|
|
||||||
* intermediate animation frames were triggering hover states and, on the
|
|
||||||
* decomposed mouseDown/moveMouse path, emitting stray `.leftMouseDragged`
|
|
||||||
* events (toolCalls.ts handleScroll's mouse_full workaround).
|
|
||||||
*/
|
|
||||||
const MOVE_SETTLE_MS = 50
|
|
||||||
|
|
||||||
async function moveAndSettle(
|
|
||||||
input: Input,
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
): Promise<void> {
|
|
||||||
await input.moveMouse(x, y, false)
|
|
||||||
await sleep(MOVE_SETTLE_MS)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Release `pressed` in reverse (last pressed = first released). Errors are
|
|
||||||
* swallowed so a release failure never masks the real error.
|
|
||||||
*
|
|
||||||
* Drains via pop() rather than snapshotting length: if a drainRunLoop-
|
|
||||||
* orphaned press lambda resolves an in-flight input.key() AFTER finally
|
|
||||||
* calls us, that late push is still released on the next iteration. The
|
|
||||||
* orphaned flag stops the lambda at its NEXT check, not the current await.
|
|
||||||
*/
|
|
||||||
async function releasePressed(input: Input, pressed: string[]): Promise<void> {
|
|
||||||
let k: string | undefined
|
|
||||||
while ((k = pressed.pop()) !== undefined) {
|
|
||||||
try {
|
|
||||||
await input.key(k, 'release')
|
|
||||||
} catch {
|
|
||||||
// Swallow — best-effort release.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bracket `fn()` with modifier press/release. `pressed` tracks which presses
|
|
||||||
* actually landed, so a mid-press throw only releases what was pressed — no
|
|
||||||
* stuck modifiers. The finally covers both press-phase and fn() throws.
|
|
||||||
*
|
|
||||||
* Caller must already be inside drainRunLoop() — key() dispatches to the
|
|
||||||
* main queue and needs the pump to resolve.
|
|
||||||
*/
|
|
||||||
async function withModifiers<T>(
|
|
||||||
input: Input,
|
|
||||||
mods: string[],
|
|
||||||
fn: () => Promise<T>,
|
|
||||||
): Promise<T> {
|
|
||||||
const pressed: string[] = []
|
|
||||||
try {
|
|
||||||
for (const m of mods) {
|
|
||||||
await input.key(m, 'press')
|
|
||||||
pressed.push(m)
|
|
||||||
}
|
|
||||||
return await fn()
|
|
||||||
} finally {
|
|
||||||
await releasePressed(input, pressed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Port of Cowork's `typeViaClipboard`. Sequence:
|
|
||||||
* 1. Save the user's clipboard.
|
|
||||||
* 2. Write our text.
|
|
||||||
* 3. READ-BACK VERIFY — clipboard writes can silently fail. If the
|
|
||||||
* read-back doesn't match, never press Cmd+V (would paste junk).
|
|
||||||
* 4. Cmd+V via keys().
|
|
||||||
* 5. Sleep 100ms — battle-tested threshold for the paste-effect vs
|
|
||||||
* clipboard-restore race. Restoring too soon means the target app
|
|
||||||
* pastes the RESTORED content.
|
|
||||||
* 6. Restore — in a `finally`, so a throw between 2-5 never leaves the
|
|
||||||
* user's clipboard clobbered. Restore failures are swallowed.
|
|
||||||
*/
|
|
||||||
async function typeViaClipboard(input: Input, text: string): Promise<void> {
|
|
||||||
let saved: string | undefined
|
let saved: string | undefined
|
||||||
try {
|
try {
|
||||||
saved = await readClipboardViaPbpaste()
|
saved = await readClipboardViaPbpaste()
|
||||||
} catch {
|
} catch {}
|
||||||
logForDebugging(
|
|
||||||
'[computer-use] pbpaste before paste failed; proceeding without restore',
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await writeClipboardViaPbcopy(text)
|
await writeClipboardViaPbcopy(text)
|
||||||
if ((await readClipboardViaPbpaste()) !== text) {
|
await callPythonHelper('key', { keySequence: 'command+v', repeat: 1 })
|
||||||
throw new Error('Clipboard write did not round-trip.')
|
|
||||||
}
|
|
||||||
await input.keys(['command', 'v'])
|
|
||||||
await sleep(100)
|
await sleep(100)
|
||||||
} finally {
|
} finally {
|
||||||
if (typeof saved === 'string') {
|
if (typeof saved === 'string') {
|
||||||
try {
|
try {
|
||||||
await writeClipboardViaPbcopy(saved)
|
await writeClipboardViaPbcopy(saved)
|
||||||
} catch {
|
} catch {}
|
||||||
logForDebugging('[computer-use] clipboard restore after paste failed')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export function createCliExecutor(_opts: {
|
||||||
* Port of Cowork's `animateMouseMovement` + `animatedMove`. Ease-out-cubic at
|
|
||||||
* 60fps; distance-proportional duration at 2000 px/sec, capped at 0.5s. When
|
|
||||||
* the sub-gate is off (or distance < ~2 frames), falls through to
|
|
||||||
* `moveAndSettle`. Called only from `drag` for the press→to motion — target
|
|
||||||
* apps may watch for `.leftMouseDragged` specifically (not just "button down +
|
|
||||||
* position changed") and the slow motion gives them time to process
|
|
||||||
* intermediate positions (scrollbars, window resizes).
|
|
||||||
*/
|
|
||||||
async function animatedMove(
|
|
||||||
input: Input,
|
|
||||||
targetX: number,
|
|
||||||
targetY: number,
|
|
||||||
mouseAnimationEnabled: boolean,
|
|
||||||
): Promise<void> {
|
|
||||||
if (!mouseAnimationEnabled) {
|
|
||||||
await moveAndSettle(input, targetX, targetY)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const start = await input.mouseLocation()
|
|
||||||
const deltaX = targetX - start.x
|
|
||||||
const deltaY = targetY - start.y
|
|
||||||
const distance = Math.hypot(deltaX, deltaY)
|
|
||||||
if (distance < 1) return
|
|
||||||
const durationSec = Math.min(distance / 2000, 0.5)
|
|
||||||
if (durationSec < 0.03) {
|
|
||||||
await moveAndSettle(input, targetX, targetY)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const frameRate = 60
|
|
||||||
const frameIntervalMs = 1000 / frameRate
|
|
||||||
const totalFrames = Math.floor(durationSec * frameRate)
|
|
||||||
for (let frame = 1; frame <= totalFrames; frame++) {
|
|
||||||
const t = frame / totalFrames
|
|
||||||
const eased = 1 - Math.pow(1 - t, 3)
|
|
||||||
await input.moveMouse(
|
|
||||||
Math.round(start.x + deltaX * eased),
|
|
||||||
Math.round(start.y + deltaY * eased),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
if (frame < totalFrames) {
|
|
||||||
await sleep(frameIntervalMs)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Last frame has no trailing sleep — same HID round-trip before the
|
|
||||||
// caller's mouseButton reads NSEvent.mouseLocation.
|
|
||||||
await sleep(MOVE_SETTLE_MS)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Factory ───────────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
export function createCliExecutor(opts: {
|
|
||||||
getMouseAnimationEnabled: () => boolean
|
getMouseAnimationEnabled: () => boolean
|
||||||
getHideBeforeActionEnabled: () => boolean
|
getHideBeforeActionEnabled: () => boolean
|
||||||
}): ComputerExecutor {
|
}): ComputerExecutor {
|
||||||
if (process.platform !== 'darwin') {
|
if (process.platform !== 'darwin') {
|
||||||
throw new Error(
|
throw new Error(`createCliExecutor called on ${process.platform}. Computer control is macOS-only.`)
|
||||||
`createCliExecutor called on ${process.platform}. Computer control is macOS-only.`,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Swift loaded once at factory time — every executor method needs it.
|
|
||||||
// Input loaded lazily via requireComputerUseInput() on first mouse/keyboard
|
|
||||||
// call — it caches internally, so screenshot-only flows never pull the
|
|
||||||
// enigo .node.
|
|
||||||
const cu = requireComputerUseSwift()
|
|
||||||
|
|
||||||
const { getMouseAnimationEnabled, getHideBeforeActionEnabled } = opts
|
|
||||||
const terminalBundleId = getTerminalBundleId()
|
|
||||||
const surrogateHost = terminalBundleId ?? CLI_HOST_BUNDLE_ID
|
|
||||||
// Swift 0.2.1's captureExcluding/captureRegion take an ALLOW list despite the
|
|
||||||
// name (apps#30355 — complement computed Swift-side against running apps).
|
|
||||||
// The terminal isn't in the user's grants so it's naturally excluded, but if
|
|
||||||
// the package ever passes it through we strip it here so the terminal never
|
|
||||||
// photobombs a screenshot.
|
|
||||||
const withoutTerminal = (allowed: readonly string[]): string[] =>
|
|
||||||
terminalBundleId === null
|
|
||||||
? [...allowed]
|
|
||||||
: allowed.filter(id => id !== terminalBundleId)
|
|
||||||
|
|
||||||
logForDebugging(
|
|
||||||
terminalBundleId
|
|
||||||
? `[computer-use] terminal ${terminalBundleId} → surrogate host (hide-exempt, activate-skip, screenshot-excluded)`
|
|
||||||
: '[computer-use] terminal not detected; falling back to sentinel host',
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
capabilities: {
|
capabilities: {
|
||||||
...CLI_CU_CAPABILITIES,
|
...CLI_CU_CAPABILITIES,
|
||||||
hostBundleId: CLI_HOST_BUNDLE_ID,
|
hostBundleId: CLI_HOST_BUNDLE_ID,
|
||||||
},
|
},
|
||||||
|
|
||||||
// ── Pre-action sequence (hide + defocus) ────────────────────────────
|
async prepareForAction(): Promise<string[]> {
|
||||||
|
return callPythonHelper('prepare_for_action', {})
|
||||||
async prepareForAction(
|
|
||||||
allowlistBundleIds: string[],
|
|
||||||
displayId?: number,
|
|
||||||
): Promise<string[]> {
|
|
||||||
if (!getHideBeforeActionEnabled()) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
// prepareDisplay isn't @MainActor (plain Task{}), but its .hide() calls
|
|
||||||
// trigger window-manager events that queue on CFRunLoop. Without the
|
|
||||||
// pump, those pile up during Swift's ~1s of usleeps and flush all at
|
|
||||||
// once when the next pumped call runs — visible window flashing.
|
|
||||||
// Electron drains CFRunLoop continuously so Cowork doesn't see this.
|
|
||||||
// Worst-case 100ms + 5×200ms safety-net ≈ 1.1s, well under the 30s
|
|
||||||
// drainRunLoop ceiling.
|
|
||||||
//
|
|
||||||
// "Continue with action execution even if switching fails" — the
|
|
||||||
// frontmost gate in toolCalls.ts catches any actual unsafe state.
|
|
||||||
return drainRunLoop(async () => {
|
|
||||||
try {
|
|
||||||
const result = await cu.apps.prepareDisplay(
|
|
||||||
allowlistBundleIds,
|
|
||||||
surrogateHost,
|
|
||||||
displayId,
|
|
||||||
)
|
|
||||||
if (result.activated) {
|
|
||||||
logForDebugging(
|
|
||||||
`[computer-use] prepareForAction: activated ${result.activated}`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return result.hidden
|
|
||||||
} catch (err) {
|
|
||||||
logForDebugging(
|
|
||||||
`[computer-use] prepareForAction failed; continuing to action: ${errorMessage(err)}`,
|
|
||||||
{ level: 'warn' },
|
|
||||||
)
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async previewHideSet(
|
async previewHideSet() {
|
||||||
allowlistBundleIds: string[],
|
return callPythonHelper('preview_hide_set', {})
|
||||||
displayId?: number,
|
|
||||||
): Promise<Array<{ bundleId: string; displayName: string }>> {
|
|
||||||
return cu.apps.previewHideSet(
|
|
||||||
[...allowlistBundleIds, surrogateHost],
|
|
||||||
displayId,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// ── Display ──────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
async getDisplaySize(displayId?: number): Promise<DisplayGeometry> {
|
async getDisplaySize(displayId?: number): Promise<DisplayGeometry> {
|
||||||
return cu.display.getSize(displayId)
|
return normalizeDisplayGeometry(await callPythonHelper('get_display_size', { displayId }))
|
||||||
},
|
},
|
||||||
|
|
||||||
async listDisplays(): Promise<DisplayGeometry[]> {
|
async listDisplays(): Promise<DisplayGeometry[]> {
|
||||||
return cu.display.listAll()
|
const displays = await callPythonHelper<PythonDisplayGeometry[]>('list_displays', {})
|
||||||
|
return displays.map(display => normalizeDisplayGeometry(display))
|
||||||
},
|
},
|
||||||
|
|
||||||
async findWindowDisplays(
|
async findWindowDisplays(bundleIds: string[]) {
|
||||||
bundleIds: string[],
|
return callPythonHelper('find_window_displays', { bundleIds })
|
||||||
): Promise<Array<{ bundleId: string; displayIds: number[] }>> {
|
|
||||||
return cu.apps.findWindowDisplays(bundleIds)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async resolvePrepareCapture(opts: {
|
async resolvePrepareCapture(opts): Promise<ResolvePrepareCaptureResult> {
|
||||||
allowedBundleIds: string[]
|
const display = await this.getDisplaySize(opts.preferredDisplayId)
|
||||||
preferredDisplayId?: number
|
const [targetW, targetH] = computeTargetDims(display.width, display.height, display.scaleFactor)
|
||||||
autoResolve: boolean
|
const result = await callPythonHelper<PythonResolvePrepareCaptureResult>('resolve_prepare_capture', {
|
||||||
doHide?: boolean
|
preferredDisplayId: opts.preferredDisplayId,
|
||||||
}): Promise<ResolvePrepareCaptureResult> {
|
targetWidth: targetW,
|
||||||
const d = cu.display.getSize(opts.preferredDisplayId)
|
targetHeight: targetH,
|
||||||
const [targetW, targetH] = computeTargetDims(
|
jpegQuality: SCREENSHOT_JPEG_QUALITY,
|
||||||
d.width,
|
})
|
||||||
d.height,
|
return {
|
||||||
d.scaleFactor,
|
...result,
|
||||||
)
|
display: normalizeDisplayGeometry(result.display),
|
||||||
return drainRunLoop(() =>
|
resolvedDisplayId: result.resolvedDisplayId ?? result.displayId,
|
||||||
cu.resolvePrepareCapture(
|
}
|
||||||
withoutTerminal(opts.allowedBundleIds),
|
|
||||||
surrogateHost,
|
|
||||||
SCREENSHOT_JPEG_QUALITY,
|
|
||||||
targetW,
|
|
||||||
targetH,
|
|
||||||
opts.preferredDisplayId,
|
|
||||||
opts.autoResolve,
|
|
||||||
opts.doHide,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
async screenshot(opts): Promise<ScreenshotResult> {
|
||||||
* Pre-size to `targetImageSize` output so the API transcoder's early-return
|
const display = await this.getDisplaySize(opts.displayId)
|
||||||
* fires — no server-side resize, `scaleCoord` stays coherent. See
|
const [targetW, targetH] = computeTargetDims(display.width, display.height, display.scaleFactor)
|
||||||
* packages/desktop/computer-use-mcp/COORDINATES.md.
|
const result = await callPythonHelper<ScreenshotResult>('screenshot', {
|
||||||
*/
|
displayId: opts.displayId,
|
||||||
async screenshot(opts: {
|
targetWidth: targetW,
|
||||||
allowedBundleIds: string[]
|
targetHeight: targetH,
|
||||||
displayId?: number
|
jpegQuality: SCREENSHOT_JPEG_QUALITY,
|
||||||
}): Promise<ScreenshotResult> {
|
})
|
||||||
const d = cu.display.getSize(opts.displayId)
|
return result
|
||||||
const [targetW, targetH] = computeTargetDims(
|
|
||||||
d.width,
|
|
||||||
d.height,
|
|
||||||
d.scaleFactor,
|
|
||||||
)
|
|
||||||
return drainRunLoop(() =>
|
|
||||||
cu.screenshot.captureExcluding(
|
|
||||||
withoutTerminal(opts.allowedBundleIds),
|
|
||||||
SCREENSHOT_JPEG_QUALITY,
|
|
||||||
targetW,
|
|
||||||
targetH,
|
|
||||||
opts.displayId,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async zoom(
|
async zoom(regionLogical, _allowedBundleIds, displayId) {
|
||||||
regionLogical: { x: number; y: number; w: number; h: number },
|
const display = await this.getDisplaySize(displayId)
|
||||||
allowedBundleIds: string[],
|
const [outW, outH] = computeTargetDims(regionLogical.w, regionLogical.h, display.scaleFactor)
|
||||||
displayId?: number,
|
return callPythonHelper('zoom', {
|
||||||
): Promise<{ base64: string; width: number; height: number }> {
|
x: regionLogical.x,
|
||||||
const d = cu.display.getSize(displayId)
|
y: regionLogical.y,
|
||||||
const [outW, outH] = computeTargetDims(
|
width: regionLogical.w,
|
||||||
regionLogical.w,
|
height: regionLogical.h,
|
||||||
regionLogical.h,
|
targetWidth: outW,
|
||||||
d.scaleFactor,
|
targetHeight: outH,
|
||||||
)
|
|
||||||
return drainRunLoop(() =>
|
|
||||||
cu.screenshot.captureRegion(
|
|
||||||
withoutTerminal(allowedBundleIds),
|
|
||||||
regionLogical.x,
|
|
||||||
regionLogical.y,
|
|
||||||
regionLogical.w,
|
|
||||||
regionLogical.h,
|
|
||||||
outW,
|
|
||||||
outH,
|
|
||||||
SCREENSHOT_JPEG_QUALITY,
|
|
||||||
displayId,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
// ── Keyboard ─────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
|
||||||
* xdotool-style sequence e.g. "ctrl+shift+a" → split on '+' and pass to
|
|
||||||
* keys(). keys() dispatches to DispatchQueue.main — drainRunLoop pumps
|
|
||||||
* CFRunLoop so it resolves. Rust's error-path cleanup (enigo_wrap.rs)
|
|
||||||
* releases modifiers on each invocation, so a mid-loop throw leaves
|
|
||||||
* nothing stuck. 8ms between iterations — 125Hz USB polling cadence.
|
|
||||||
*/
|
|
||||||
async key(keySequence: string, repeat?: number): Promise<void> {
|
|
||||||
const input = requireComputerUseInput()
|
|
||||||
const parts = keySequence.split('+').filter(p => p.length > 0)
|
|
||||||
// Bare-only: the CGEventTap checks event.flags.isEmpty so ctrl+escape
|
|
||||||
// etc. pass through without aborting.
|
|
||||||
const isEsc = isBareEscape(parts)
|
|
||||||
const n = repeat ?? 1
|
|
||||||
await drainRunLoop(async () => {
|
|
||||||
for (let i = 0; i < n; i++) {
|
|
||||||
if (i > 0) {
|
|
||||||
await sleep(8)
|
|
||||||
}
|
|
||||||
if (isEsc) {
|
|
||||||
notifyExpectedEscape()
|
|
||||||
}
|
|
||||||
await input.keys(parts)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
async holdKey(keyNames: string[], durationMs: number): Promise<void> {
|
async key(keySequence: string, repeat?: number): Promise<void> {
|
||||||
const input = requireComputerUseInput()
|
await callPythonHelper('key', { keySequence, repeat: repeat ?? 1 })
|
||||||
// Press/release each wrapped in drainRunLoop; the sleep sits outside so
|
|
||||||
// durationMs isn't bounded by drainRunLoop's 30s timeout. `pressed`
|
|
||||||
// tracks which presses landed so a mid-press throw still releases
|
|
||||||
// everything that was actually pressed.
|
|
||||||
//
|
|
||||||
// `orphaned` guards against a timeout-orphan race: if the press-phase
|
|
||||||
// drainRunLoop times out while the esc-hotkey pump-retain keeps the
|
|
||||||
// pump running, the orphaned lambda would continue pushing to `pressed`
|
|
||||||
// after finally's releasePressed snapshotted the length — leaving keys
|
|
||||||
// stuck. The flag stops the lambda at the next iteration.
|
|
||||||
const pressed: string[] = []
|
|
||||||
let orphaned = false
|
|
||||||
try {
|
|
||||||
await drainRunLoop(async () => {
|
|
||||||
for (const k of keyNames) {
|
|
||||||
if (orphaned) return
|
|
||||||
// Bare Escape: notify the CGEventTap so it doesn't fire the
|
|
||||||
// abort callback for a model-synthesized press. Same as key().
|
|
||||||
if (isBareEscape([k])) {
|
|
||||||
notifyExpectedEscape()
|
|
||||||
}
|
|
||||||
await input.key(k, 'press')
|
|
||||||
pressed.push(k)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
await sleep(durationMs)
|
|
||||||
} finally {
|
|
||||||
orphaned = true
|
|
||||||
await drainRunLoop(() => releasePressed(input, pressed))
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async type(text: string, opts: { viaClipboard: boolean }): Promise<void> {
|
async holdKey(keyNames: string[], durationMs: number): Promise<void> {
|
||||||
const input = requireComputerUseInput()
|
await callPythonHelper('hold_key', { keyNames, durationMs })
|
||||||
if (opts.viaClipboard) {
|
},
|
||||||
// keys(['command','v']) inside needs the pump.
|
|
||||||
await drainRunLoop(() => typeViaClipboard(input, text))
|
async type(text: string, opts2: { viaClipboard: boolean }): Promise<void> {
|
||||||
|
if (opts2.viaClipboard) {
|
||||||
|
await typeViaClipboard(text)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// `toolCalls.ts` handles the grapheme loop + 8ms sleeps and calls this
|
await callPythonHelper('type', { text })
|
||||||
// once per grapheme. typeText doesn't dispatch to the main queue.
|
|
||||||
await input.typeText(text)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
readClipboard: readClipboardViaPbpaste,
|
readClipboard: readClipboardViaPbpaste,
|
||||||
|
|
||||||
writeClipboard: writeClipboardViaPbcopy,
|
writeClipboard: writeClipboardViaPbcopy,
|
||||||
|
|
||||||
// ── Mouse ────────────────────────────────────────────────────────────
|
async click(x, y, button, count, modifiers): Promise<void> {
|
||||||
|
await callPythonHelper('click', { x, y, button, count, modifiers })
|
||||||
async moveMouse(x: number, y: number): Promise<void> {
|
await sleep(MOVE_SETTLE_MS)
|
||||||
await moveAndSettle(requireComputerUseInput(), x, y)
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Move, then click. Modifiers are press/release bracketed via withModifiers
|
|
||||||
* — same pattern as Cowork. AppKit computes NSEvent.clickCount from timing
|
|
||||||
* + position proximity, so double/triple click work without setting the
|
|
||||||
* CGEvent clickState field. key() inside withModifiers needs the pump;
|
|
||||||
* the modifier-less path doesn't.
|
|
||||||
*/
|
|
||||||
async click(
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
button: 'left' | 'right' | 'middle',
|
|
||||||
count: 1 | 2 | 3,
|
|
||||||
modifiers?: string[],
|
|
||||||
): Promise<void> {
|
|
||||||
const input = requireComputerUseInput()
|
|
||||||
await moveAndSettle(input, x, y)
|
|
||||||
if (modifiers && modifiers.length > 0) {
|
|
||||||
await drainRunLoop(() =>
|
|
||||||
withModifiers(input, modifiers, () =>
|
|
||||||
input.mouseButton(button, 'click', count),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
await input.mouseButton(button, 'click', count)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async mouseDown(): Promise<void> {
|
async mouseDown(): Promise<void> {
|
||||||
await requireComputerUseInput().mouseButton('left', 'press')
|
await callPythonHelper('mouse_down', {})
|
||||||
},
|
},
|
||||||
|
|
||||||
async mouseUp(): Promise<void> {
|
async mouseUp(): Promise<void> {
|
||||||
await requireComputerUseInput().mouseButton('left', 'release')
|
await callPythonHelper('mouse_up', {})
|
||||||
},
|
},
|
||||||
|
|
||||||
async getCursorPosition(): Promise<{ x: number; y: number }> {
|
async getCursorPosition(): Promise<{ x: number; y: number }> {
|
||||||
return requireComputerUseInput().mouseLocation()
|
return callPythonHelper('cursor_position', {})
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
async drag(from, to): Promise<void> {
|
||||||
* `from === undefined` → drag from current cursor (training's
|
await callPythonHelper('drag', { from, to })
|
||||||
* left_click_drag with start_coordinate omitted). Inner `finally`: the
|
|
||||||
* button is ALWAYS released even if the move throws — otherwise the
|
|
||||||
* user's left button is stuck-pressed until they physically click.
|
|
||||||
* 50ms sleep after press: enigo's move_mouse reads NSEvent.pressedMouseButtons
|
|
||||||
* to decide .leftMouseDragged vs .mouseMoved; the synthetic leftMouseDown
|
|
||||||
* needs a HID-tap round-trip to show up there.
|
|
||||||
*/
|
|
||||||
async drag(
|
|
||||||
from: { x: number; y: number } | undefined,
|
|
||||||
to: { x: number; y: number },
|
|
||||||
): Promise<void> {
|
|
||||||
const input = requireComputerUseInput()
|
|
||||||
if (from !== undefined) {
|
|
||||||
await moveAndSettle(input, from.x, from.y)
|
|
||||||
}
|
|
||||||
await input.mouseButton('left', 'press')
|
|
||||||
await sleep(MOVE_SETTLE_MS)
|
await sleep(MOVE_SETTLE_MS)
|
||||||
try {
|
|
||||||
await animatedMove(input, to.x, to.y, getMouseAnimationEnabled())
|
|
||||||
} finally {
|
|
||||||
await input.mouseButton('left', 'release')
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
async moveMouse(x, y): Promise<void> {
|
||||||
* Move first, then scroll each axis. Vertical-first — it's the common
|
await callPythonHelper('move_mouse', { x, y })
|
||||||
* axis; a horizontal failure shouldn't lose the vertical.
|
await sleep(MOVE_SETTLE_MS)
|
||||||
*/
|
|
||||||
async scroll(x: number, y: number, dx: number, dy: number): Promise<void> {
|
|
||||||
const input = requireComputerUseInput()
|
|
||||||
await moveAndSettle(input, x, y)
|
|
||||||
if (dy !== 0) {
|
|
||||||
await input.mouseScroll(dy, 'vertical')
|
|
||||||
}
|
|
||||||
if (dx !== 0) {
|
|
||||||
await input.mouseScroll(dx, 'horizontal')
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// ── App management ───────────────────────────────────────────────────
|
async scroll(x, y, dx, dy): Promise<void> {
|
||||||
|
await callPythonHelper('scroll', { x, y, deltaX: dx, deltaY: dy })
|
||||||
|
},
|
||||||
|
|
||||||
async getFrontmostApp(): Promise<FrontmostApp | null> {
|
async getFrontmostApp(): Promise<FrontmostApp | null> {
|
||||||
const info = requireComputerUseInput().getFrontmostAppInfo()
|
return callPythonHelper('frontmost_app', {})
|
||||||
if (!info || !info.bundleId) return null
|
|
||||||
return { bundleId: info.bundleId, displayName: info.appName }
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async appUnderPoint(
|
async appUnderPoint(x, y) {
|
||||||
x: number,
|
return callPythonHelper('app_under_point', { x, y })
|
||||||
y: number,
|
|
||||||
): Promise<{ bundleId: string; displayName: string } | null> {
|
|
||||||
return cu.apps.appUnderPoint(x, y)
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async listInstalledApps(): Promise<InstalledApp[]> {
|
async listInstalledApps(): Promise<InstalledApp[]> {
|
||||||
// `ComputerUseInstalledApp` is `{bundleId, displayName, path}`.
|
return callPythonHelper('list_installed_apps', {})
|
||||||
// `InstalledApp` adds optional `iconDataUrl` — left unpopulated;
|
|
||||||
// the approval dialog fetches lazily via getAppIcon() below.
|
|
||||||
return drainRunLoop(() => cu.apps.listInstalled())
|
|
||||||
},
|
|
||||||
|
|
||||||
async getAppIcon(path: string): Promise<string | undefined> {
|
|
||||||
return cu.apps.iconDataUrl(path) ?? undefined
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async listRunningApps(): Promise<RunningApp[]> {
|
async listRunningApps(): Promise<RunningApp[]> {
|
||||||
return cu.apps.listRunning()
|
return callPythonHelper('list_running_apps', {})
|
||||||
},
|
},
|
||||||
|
|
||||||
async openApp(bundleId: string): Promise<void> {
|
async openApp(bundleId: string): Promise<void> {
|
||||||
await cu.apps.open(bundleId)
|
await callPythonHelper('open_app', { bundleId })
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export async function unhideComputerUseApps(_bundleIds: readonly string[]): Promise<void> {
|
||||||
* Module-level export (not on the executor object) — called at turn-end from
|
return
|
||||||
* `stopHooks.ts` / `query.ts`, outside the executor lifecycle. Fire-and-forget
|
|
||||||
* at the call site; the caller `.catch()`es.
|
|
||||||
*/
|
|
||||||
export async function unhideComputerUseApps(
|
|
||||||
bundleIds: readonly string[],
|
|
||||||
): Promise<void> {
|
|
||||||
if (bundleIds.length === 0) return
|
|
||||||
const cu = requireComputerUseSwift()
|
|
||||||
await cu.apps.unhide([...bundleIds])
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
import type { CoordinateMode, CuSubGates } from '@ant/computer-use-mcp/types'
|
import type { CoordinateMode, CuSubGates } from '../../vendor/computer-use-mcp/types.js'
|
||||||
|
|
||||||
import { getDynamicConfig_CACHED_MAY_BE_STALE } from '../../services/analytics/growthbook.js'
|
import { getDynamicConfig_CACHED_MAY_BE_STALE } from '../../services/analytics/growthbook.js'
|
||||||
import { getSubscriptionType } from '../auth.js'
|
|
||||||
import { isEnvTruthy } from '../envUtils.js'
|
|
||||||
|
|
||||||
type ChicagoConfig = CuSubGates & {
|
type ChicagoConfig = CuSubGates & {
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
@ -10,7 +8,7 @@ type ChicagoConfig = CuSubGates & {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULTS: ChicagoConfig = {
|
const DEFAULTS: ChicagoConfig = {
|
||||||
enabled: false,
|
enabled: true,
|
||||||
pixelValidation: false,
|
pixelValidation: false,
|
||||||
clipboardPasteMultiline: true,
|
clipboardPasteMultiline: true,
|
||||||
mouseAnimation: true,
|
mouseAnimation: true,
|
||||||
@ -33,28 +31,8 @@ function readConfig(): ChicagoConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Max/Pro only for external rollout. Ant bypass so dogfooding continues
|
|
||||||
// regardless of subscription tier — not all ants are max/pro, and per
|
|
||||||
// CLAUDE.md:281, USER_TYPE !== 'ant' branches get zero antfooding.
|
|
||||||
function hasRequiredSubscription(): boolean {
|
|
||||||
if (process.env.USER_TYPE === 'ant') return true
|
|
||||||
const tier = getSubscriptionType()
|
|
||||||
return tier === 'max' || tier === 'pro'
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getChicagoEnabled(): boolean {
|
export function getChicagoEnabled(): boolean {
|
||||||
// Disable for ants whose shell inherited monorepo dev config.
|
return true
|
||||||
// MONOREPO_ROOT_DIR is exported by config/local/zsh/zshrc, which
|
|
||||||
// laptop-setup.sh wires into ~/.zshrc — its presence is the cheap
|
|
||||||
// proxy for "has monorepo access". Override: ALLOW_ANT_COMPUTER_USE_MCP=1.
|
|
||||||
if (
|
|
||||||
process.env.USER_TYPE === 'ant' &&
|
|
||||||
process.env.MONOREPO_ROOT_DIR &&
|
|
||||||
!isEnvTruthy(process.env.ALLOW_ANT_COMPUTER_USE_MCP)
|
|
||||||
) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return hasRequiredSubscription() && readConfig().enabled
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getChicagoSubGates(): CuSubGates {
|
export function getChicagoSubGates(): CuSubGates {
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import type {
|
import type {
|
||||||
ComputerUseHostAdapter,
|
ComputerUseHostAdapter,
|
||||||
Logger,
|
Logger,
|
||||||
} from '@ant/computer-use-mcp/types'
|
} from '../../vendor/computer-use-mcp/types.js'
|
||||||
import { format } from 'util'
|
import { format } from 'util'
|
||||||
import { logForDebugging } from '../debug.js'
|
import { logForDebugging } from '../debug.js'
|
||||||
import { COMPUTER_USE_MCP_SERVER_NAME } from './common.js'
|
import { COMPUTER_USE_MCP_SERVER_NAME } from './common.js'
|
||||||
import { createCliExecutor } from './executor.js'
|
import { createCliExecutor } from './executor.js'
|
||||||
import { getChicagoEnabled, getChicagoSubGates } from './gates.js'
|
import { getChicagoEnabled, getChicagoSubGates } from './gates.js'
|
||||||
import { requireComputerUseSwift } from './swiftLoader.js'
|
import { callPythonHelper } from './pythonBridge.js'
|
||||||
|
|
||||||
class DebugLogger implements Logger {
|
class DebugLogger implements Logger {
|
||||||
silly(message: string, ...args: unknown[]): void {
|
silly(message: string, ...args: unknown[]): void {
|
||||||
@ -29,12 +29,6 @@ class DebugLogger implements Logger {
|
|||||||
|
|
||||||
let cached: ComputerUseHostAdapter | undefined
|
let cached: ComputerUseHostAdapter | undefined
|
||||||
|
|
||||||
/**
|
|
||||||
* Process-lifetime singleton. Built once on first CU tool call; native modules
|
|
||||||
* (both `@ant/computer-use-input` and `@ant/computer-use-swift`) are loaded
|
|
||||||
* here via the executor factory, which throws on load failure — there is no
|
|
||||||
* degraded mode.
|
|
||||||
*/
|
|
||||||
export function getComputerUseHostAdapter(): ComputerUseHostAdapter {
|
export function getComputerUseHostAdapter(): ComputerUseHostAdapter {
|
||||||
if (cached) return cached
|
if (cached) return cached
|
||||||
cached = {
|
cached = {
|
||||||
@ -45,24 +39,14 @@ export function getComputerUseHostAdapter(): ComputerUseHostAdapter {
|
|||||||
getHideBeforeActionEnabled: () => getChicagoSubGates().hideBeforeAction,
|
getHideBeforeActionEnabled: () => getChicagoSubGates().hideBeforeAction,
|
||||||
}),
|
}),
|
||||||
ensureOsPermissions: async () => {
|
ensureOsPermissions: async () => {
|
||||||
const cu = requireComputerUseSwift()
|
const perms = await callPythonHelper<{ accessibility: boolean; screenRecording: boolean }>('check_permissions', {})
|
||||||
const accessibility = cu.tcc.checkAccessibility()
|
return perms.accessibility && perms.screenRecording
|
||||||
const screenRecording = cu.tcc.checkScreenRecording()
|
? { granted: true as const }
|
||||||
return accessibility && screenRecording
|
: { granted: false as const, accessibility: perms.accessibility, screenRecording: perms.screenRecording }
|
||||||
? { granted: true }
|
|
||||||
: { granted: false, accessibility, screenRecording }
|
|
||||||
},
|
},
|
||||||
isDisabled: () => !getChicagoEnabled(),
|
isDisabled: () => !getChicagoEnabled(),
|
||||||
getSubGates: getChicagoSubGates,
|
getSubGates: getChicagoSubGates,
|
||||||
// cleanup.ts always unhides at turn end — no user preference to disable it.
|
|
||||||
getAutoUnhideEnabled: () => true,
|
getAutoUnhideEnabled: () => true,
|
||||||
|
|
||||||
// Pixel-validation JPEG decode+crop. MUST be synchronous (the package
|
|
||||||
// does `patch1.equals(patch2)` directly on the return value). Cowork uses
|
|
||||||
// Electron's `nativeImage` (sync); our `image-processor-napi` is
|
|
||||||
// sharp-compatible and async-only. Returning null → validation skipped,
|
|
||||||
// click proceeds — the designed fallback per `PixelCompareResult.skipped`.
|
|
||||||
// The sub-gate defaults to false anyway.
|
|
||||||
cropRawPatch: () => null,
|
cropRawPatch: () => null,
|
||||||
}
|
}
|
||||||
return cached
|
return cached
|
||||||
|
|||||||
@ -1,30 +1,3 @@
|
|||||||
import type {
|
export function requireComputerUseInput(): any {
|
||||||
ComputerUseInput,
|
throw new Error('Native input module replaced by Python bridge. See pythonBridge.ts')
|
||||||
ComputerUseInputAPI,
|
|
||||||
} from '@ant/computer-use-input'
|
|
||||||
|
|
||||||
let cached: ComputerUseInputAPI | undefined
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Package's js/index.js reads COMPUTER_USE_INPUT_NODE_PATH (baked by
|
|
||||||
* build-with-plugins.ts on darwin targets, unset otherwise — falls through to
|
|
||||||
* the node_modules prebuilds/ path).
|
|
||||||
*
|
|
||||||
* The package exports a discriminated union on `isSupported` — narrowed here
|
|
||||||
* once so callers get the bare `ComputerUseInputAPI` without re-checking.
|
|
||||||
*
|
|
||||||
* key()/keys() dispatch enigo work onto DispatchQueue.main via
|
|
||||||
* dispatch2::run_on_main, then block a tokio worker on a channel. Under
|
|
||||||
* Electron (CFRunLoop drains the main queue) this works; under libuv
|
|
||||||
* (Node/bun) the main queue never drains and the promise hangs. The executor
|
|
||||||
* calls these inside drainRunLoop().
|
|
||||||
*/
|
|
||||||
export function requireComputerUseInput(): ComputerUseInputAPI {
|
|
||||||
if (cached) return cached
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
||||||
const input = require('@ant/computer-use-input') as ComputerUseInput
|
|
||||||
if (!input.isSupported) {
|
|
||||||
throw new Error('@ant/computer-use-input is not supported on this platform')
|
|
||||||
}
|
|
||||||
return (cached = input)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
buildComputerUseTools,
|
buildComputerUseTools,
|
||||||
createComputerUseMcpServer,
|
createComputerUseMcpServer,
|
||||||
} from '@ant/computer-use-mcp'
|
} from '../../vendor/computer-use-mcp/index.js'
|
||||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
||||||
import { ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'
|
import { ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'
|
||||||
import { homedir } from 'os'
|
import { homedir } from 'os'
|
||||||
|
|||||||
110
src/utils/computerUse/pythonBridge.ts
Normal file
110
src/utils/computerUse/pythonBridge.ts
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import { createHash } from 'node:crypto'
|
||||||
|
import { readFile, mkdir, access, writeFile } from 'node:fs/promises'
|
||||||
|
import path from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
import { execFileNoThrow } from '../execFileNoThrow.js'
|
||||||
|
import { logForDebugging } from '../debug.js'
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||||
|
const projectRoot = path.resolve(__dirname, '../../..')
|
||||||
|
const runtimeRoot = path.join(projectRoot, 'runtime')
|
||||||
|
const runtimeStateRoot = path.join(projectRoot, '.runtime')
|
||||||
|
const venvRoot = path.join(runtimeStateRoot, 'venv')
|
||||||
|
const requirementsPath = path.join(runtimeRoot, 'requirements.txt')
|
||||||
|
const helperPath = path.join(runtimeRoot, 'mac_helper.py')
|
||||||
|
const installStampPath = path.join(runtimeStateRoot, 'requirements.sha256')
|
||||||
|
|
||||||
|
let bootstrapPromise: Promise<void> | undefined
|
||||||
|
|
||||||
|
function pythonBinPath(): string {
|
||||||
|
return path.join(venvRoot, 'bin', 'python3')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pathExists(target: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
await access(target)
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runOrThrow(file: string, args: string[], label: string): Promise<string> {
|
||||||
|
const { code, stdout, stderr } = await execFileNoThrow(file, args, { useCwd: false })
|
||||||
|
if (code !== 0) {
|
||||||
|
throw new Error(`${label} failed with code ${code}: ${stderr || stdout || 'unknown error'}`)
|
||||||
|
}
|
||||||
|
return stdout
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function ensureBootstrapped(): Promise<void> {
|
||||||
|
if (bootstrapPromise) return bootstrapPromise
|
||||||
|
bootstrapPromise = (async () => {
|
||||||
|
await mkdir(runtimeStateRoot, { recursive: true })
|
||||||
|
|
||||||
|
if (!(await pathExists(pythonBinPath()))) {
|
||||||
|
logForDebugging('creating runtime venv at %s', { level: 'debug' })
|
||||||
|
await runOrThrow('python3', ['-m', 'venv', venvRoot], 'python venv creation')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await pathExists(path.join(venvRoot, 'bin', 'pip')))) {
|
||||||
|
logForDebugging('bootstrapping pip with ensurepip', { level: 'debug' })
|
||||||
|
await runOrThrow(pythonBinPath(), ['-m', 'ensurepip', '--upgrade'], 'ensurepip')
|
||||||
|
}
|
||||||
|
|
||||||
|
const requirements = await readFile(requirementsPath, 'utf8')
|
||||||
|
const digest = createHash('sha256').update(requirements).digest('hex')
|
||||||
|
let installedDigest = ''
|
||||||
|
try {
|
||||||
|
installedDigest = (await readFile(installStampPath, 'utf8')).trim()
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
if (installedDigest !== digest) {
|
||||||
|
logForDebugging('installing python runtime dependencies', { level: 'debug' })
|
||||||
|
await runOrThrow(pythonBinPath(), ['-m', 'pip', 'install', '--upgrade', 'pip'], 'pip upgrade')
|
||||||
|
await runOrThrow(
|
||||||
|
pythonBinPath(),
|
||||||
|
['-m', 'pip', 'install', '-r', requirementsPath],
|
||||||
|
'python dependency install',
|
||||||
|
)
|
||||||
|
await writeFile(installStampPath, `${digest}\n`, 'utf8')
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
try {
|
||||||
|
await bootstrapPromise
|
||||||
|
} catch (error) {
|
||||||
|
bootstrapPromise = undefined
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function callPythonHelper<T>(command: string, payload: Record<string, unknown> = {}): Promise<T> {
|
||||||
|
await ensureBootstrapped()
|
||||||
|
const { code, stdout, stderr } = await execFileNoThrow(
|
||||||
|
pythonBinPath(),
|
||||||
|
[helperPath, command, '--payload', JSON.stringify(payload)],
|
||||||
|
{ useCwd: false },
|
||||||
|
)
|
||||||
|
|
||||||
|
if (code !== 0 && !stdout.trim()) {
|
||||||
|
throw new Error(stderr || `Python helper ${command} failed with code ${code}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
let parsed: { ok: boolean; result?: T; error?: { message?: string } }
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(stdout)
|
||||||
|
} catch {
|
||||||
|
throw new Error(stderr || stdout || `Python helper ${command} returned invalid JSON`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parsed.ok) {
|
||||||
|
throw new Error(parsed.error?.message || `Python helper ${command} failed`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed.result as T
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRuntimePaths(): { projectRoot: string; runtimeRoot: string; venvRoot: string } {
|
||||||
|
return { projectRoot, runtimeRoot, venvRoot }
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { buildComputerUseTools } from '@ant/computer-use-mcp'
|
import { buildComputerUseTools } from '../../vendor/computer-use-mcp/index.js'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { fileURLToPath } from 'url'
|
import { fileURLToPath } from 'url'
|
||||||
import { buildMcpToolName } from '../../services/mcp/mcpStringUtils.js'
|
import { buildMcpToolName } from '../../services/mcp/mcpStringUtils.js'
|
||||||
|
|||||||
@ -1,23 +1,3 @@
|
|||||||
import type { ComputerUseAPI } from '@ant/computer-use-swift'
|
export function requireComputerUseSwift(): any {
|
||||||
|
throw new Error('Native Swift module replaced by Python bridge. See pythonBridge.ts')
|
||||||
let cached: ComputerUseAPI | undefined
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Package's js/index.js reads COMPUTER_USE_SWIFT_NODE_PATH (baked by
|
|
||||||
* build-with-plugins.ts on darwin targets, unset otherwise — falls through to
|
|
||||||
* the node_modules prebuilds/ path). We cache the loaded native module.
|
|
||||||
*
|
|
||||||
* The four @MainActor methods (captureExcluding, captureRegion,
|
|
||||||
* apps.listInstalled, resolvePrepareCapture) dispatch to DispatchQueue.main
|
|
||||||
* and will hang under libuv unless CFRunLoop is pumped — call sites wrap
|
|
||||||
* these in drainRunLoop().
|
|
||||||
*/
|
|
||||||
export function requireComputerUseSwift(): ComputerUseAPI {
|
|
||||||
if (process.platform !== 'darwin') {
|
|
||||||
throw new Error('@ant/computer-use-swift is macOS-only')
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
||||||
return (cached ??= require('@ant/computer-use-swift') as ComputerUseAPI)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type { ComputerUseAPI }
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@
|
|||||||
* GrowthBook gate `tengu_malort_pedway` (see gates.ts).
|
* GrowthBook gate `tengu_malort_pedway` (see gates.ts).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { bindSessionContext, type ComputerUseSessionContext, type CuCallToolResult, type CuPermissionRequest, type CuPermissionResponse, DEFAULT_GRANT_FLAGS, type ScreenshotDims } from '@ant/computer-use-mcp';
|
import { bindSessionContext, type ComputerUseSessionContext, type CuCallToolResult, type CuPermissionRequest, type CuPermissionResponse, DEFAULT_GRANT_FLAGS, type ScreenshotDims } from '../../vendor/computer-use-mcp/index.js';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { getSessionId } from '../../bootstrap/state.js';
|
import { getSessionId } from '../../bootstrap/state.js';
|
||||||
import { ComputerUseApproval } from '../../components/permissions/ComputerUseApproval/ComputerUseApproval.js';
|
import { ComputerUseApproval } from '../../components/permissions/ComputerUseApproval/ComputerUseApproval.js';
|
||||||
|
|||||||
553
src/vendor/computer-use-mcp/deniedApps.ts
vendored
Normal file
553
src/vendor/computer-use-mcp/deniedApps.ts
vendored
Normal file
@ -0,0 +1,553 @@
|
|||||||
|
/**
|
||||||
|
* App category lookup for tiered CU permissions. Three categories land at a
|
||||||
|
* restricted tier instead of `"full"`:
|
||||||
|
*
|
||||||
|
* - **browser** → `"read"` tier — visible in screenshots, NO interaction.
|
||||||
|
* The model can read an already-open page but must use the Claude-in-Chrome
|
||||||
|
* MCP for navigation/clicking/typing.
|
||||||
|
* - **terminal** → `"click"` tier — visible + clickable, NO typing. The
|
||||||
|
* model can click a Run button or scroll test output in an IDE, but can't
|
||||||
|
* type into the integrated terminal. Use the Bash tool for shell work.
|
||||||
|
* - **trading** → `"read"` tier — same restrictions as browsers, but no
|
||||||
|
* CiC-MCP alternative exists. For platforms where a stray click can
|
||||||
|
* execute a trade or send a message to a counterparty.
|
||||||
|
*
|
||||||
|
* Uncategorized apps default to `"full"`. See `getDefaultTierForApp`.
|
||||||
|
*
|
||||||
|
* Identification is two-layered:
|
||||||
|
* 1. Bundle ID match (macOS-only; `InstalledApp.bundleId` is a
|
||||||
|
* CFBundleIdentifier and meaningless on Windows). Fast, exact, the
|
||||||
|
* primary mechanism while CU is darwin-gated.
|
||||||
|
* 2. Display-name substring match (cross-platform fallback). Catches
|
||||||
|
* unresolved requests ("Chrome" when Chrome isn't installed) AND will
|
||||||
|
* be the primary mechanism on Windows/Linux where there's no bundle ID.
|
||||||
|
* Windows-relevant names (PowerShell, cmd, Windows Terminal) are
|
||||||
|
* included now so they activate the moment the darwin gate lifts.
|
||||||
|
*
|
||||||
|
* Keep this file **import-free** (like sentinelApps.ts) — the renderer may
|
||||||
|
* import it via a package.json subpath export, and pulling in
|
||||||
|
* `@modelcontextprotocol/sdk` (a devDep) through the index → mcpServer chain
|
||||||
|
* would fail module resolution in Next.js. The `CuAppPermTier` type is
|
||||||
|
* duplicated as a string literal below rather than imported.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type DeniedCategory = "browser" | "terminal" | "trading";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map a category to its hardcoded tier. Return-type is the string-literal
|
||||||
|
* union inline (this file is import-free; see header comment). The
|
||||||
|
* authoritative type is `CuAppPermTier` in types.ts — keep in sync.
|
||||||
|
*
|
||||||
|
* Not bijective — both `"browser"` and `"trading"` map to `"read"`. Copy
|
||||||
|
* that differs by category (the "use CiC" hint is browser-only) must check
|
||||||
|
* the category, not just the tier.
|
||||||
|
*/
|
||||||
|
export function categoryToTier(
|
||||||
|
category: DeniedCategory | null,
|
||||||
|
): "read" | "click" | "full" {
|
||||||
|
if (category === "browser" || category === "trading") return "read";
|
||||||
|
if (category === "terminal") return "click";
|
||||||
|
return "full";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Bundle-ID deny sets (macOS) ─────────────────────────────────────────
|
||||||
|
|
||||||
|
const BROWSER_BUNDLE_IDS: ReadonlySet<string> = new Set([
|
||||||
|
// Apple
|
||||||
|
"com.apple.Safari",
|
||||||
|
"com.apple.SafariTechnologyPreview",
|
||||||
|
// Google
|
||||||
|
"com.google.Chrome",
|
||||||
|
"com.google.Chrome.beta",
|
||||||
|
"com.google.Chrome.dev",
|
||||||
|
"com.google.Chrome.canary",
|
||||||
|
// Microsoft
|
||||||
|
"com.microsoft.edgemac",
|
||||||
|
"com.microsoft.edgemac.Beta",
|
||||||
|
"com.microsoft.edgemac.Dev",
|
||||||
|
"com.microsoft.edgemac.Canary",
|
||||||
|
// Mozilla
|
||||||
|
"org.mozilla.firefox",
|
||||||
|
"org.mozilla.firefoxdeveloperedition",
|
||||||
|
"org.mozilla.nightly",
|
||||||
|
// Chromium-based
|
||||||
|
"org.chromium.Chromium",
|
||||||
|
"com.brave.Browser",
|
||||||
|
"com.brave.Browser.beta",
|
||||||
|
"com.brave.Browser.nightly",
|
||||||
|
"com.operasoftware.Opera",
|
||||||
|
"com.operasoftware.OperaGX",
|
||||||
|
"com.operasoftware.OperaDeveloper",
|
||||||
|
"com.vivaldi.Vivaldi",
|
||||||
|
// The Browser Company
|
||||||
|
"company.thebrowser.Browser", // Arc
|
||||||
|
"company.thebrowser.dia", // Dia (agentic)
|
||||||
|
// Privacy-focused
|
||||||
|
"org.torproject.torbrowser",
|
||||||
|
"com.duckduckgo.macos.browser",
|
||||||
|
"ru.yandex.desktop.yandex-browser",
|
||||||
|
// Agentic / AI browsers — newer entrants with LLM integrations
|
||||||
|
"ai.perplexity.comet",
|
||||||
|
"com.sigmaos.sigmaos.macos", // SigmaOS
|
||||||
|
// Webkit-based misc
|
||||||
|
"com.kagi.kagimacOS", // Orion
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminals + IDEs with integrated terminals. Supersets
|
||||||
|
* `SHELL_ACCESS_BUNDLE_IDS` from sentinelApps.ts — terminals proceed to the
|
||||||
|
* approval dialog at tier "click", and the sentinel warning renders
|
||||||
|
* alongside the tier badge.
|
||||||
|
*/
|
||||||
|
const TERMINAL_BUNDLE_IDS: ReadonlySet<string> = new Set([
|
||||||
|
// Dedicated terminals
|
||||||
|
"com.apple.Terminal",
|
||||||
|
"com.googlecode.iterm2",
|
||||||
|
"dev.warp.Warp-Stable",
|
||||||
|
"dev.warp.Warp-Beta",
|
||||||
|
"com.github.wez.wezterm",
|
||||||
|
"org.alacritty",
|
||||||
|
"io.alacritty", // pre-v0.11.0 (renamed 2022-07) — kept for legacy installs
|
||||||
|
"net.kovidgoyal.kitty",
|
||||||
|
"co.zeit.hyper",
|
||||||
|
"com.mitchellh.ghostty",
|
||||||
|
"org.tabby",
|
||||||
|
"com.termius-dmg.mac", // Termius
|
||||||
|
// IDEs with integrated terminals — we can't distinguish "type in the
|
||||||
|
// editor" from "type in the integrated terminal" via screenshot+click.
|
||||||
|
// VS Code family
|
||||||
|
"com.microsoft.VSCode",
|
||||||
|
"com.microsoft.VSCodeInsiders",
|
||||||
|
"com.vscodium", // VSCodium
|
||||||
|
"com.todesktop.230313mzl4w4u92", // Cursor
|
||||||
|
"com.exafunction.windsurf", // Windsurf / Codeium
|
||||||
|
"dev.zed.Zed",
|
||||||
|
"dev.zed.Zed-Preview",
|
||||||
|
// JetBrains family (all have integrated terminals)
|
||||||
|
"com.jetbrains.intellij",
|
||||||
|
"com.jetbrains.intellij.ce",
|
||||||
|
"com.jetbrains.pycharm",
|
||||||
|
"com.jetbrains.pycharm.ce",
|
||||||
|
"com.jetbrains.WebStorm",
|
||||||
|
"com.jetbrains.CLion",
|
||||||
|
"com.jetbrains.goland",
|
||||||
|
"com.jetbrains.rubymine",
|
||||||
|
"com.jetbrains.PhpStorm",
|
||||||
|
"com.jetbrains.datagrip",
|
||||||
|
"com.jetbrains.rider",
|
||||||
|
"com.jetbrains.AppCode",
|
||||||
|
"com.jetbrains.rustrover",
|
||||||
|
"com.jetbrains.fleet",
|
||||||
|
"com.google.android.studio", // Android Studio (JetBrains-based)
|
||||||
|
// Other IDEs
|
||||||
|
"com.axosoft.gitkraken", // GitKraken has an integrated terminal panel. Also keeps the "kraken" trading-substring from miscategorizing it — bundle-ID wins.
|
||||||
|
"com.sublimetext.4",
|
||||||
|
"com.sublimetext.3",
|
||||||
|
"org.vim.MacVim",
|
||||||
|
"com.neovim.neovim",
|
||||||
|
"org.gnu.Emacs",
|
||||||
|
// Xcode's previous carve-out (full tier for Interface Builder / simulator)
|
||||||
|
// was reversed — at tier "click" IB and simulator taps still work (both are
|
||||||
|
// plain clicks) while the integrated terminal is blocked from keyboard input.
|
||||||
|
"com.apple.dt.Xcode",
|
||||||
|
"org.eclipse.platform.ide",
|
||||||
|
"org.netbeans.ide",
|
||||||
|
"com.microsoft.visual-studio", // Visual Studio for Mac
|
||||||
|
// AppleScript/automation execution surfaces — same threat as terminals:
|
||||||
|
// type(script) → key("cmd+r") runs arbitrary code. Added after #28011
|
||||||
|
// removed the osascript MCP server, making CU the only tool-call route
|
||||||
|
// to AppleScript.
|
||||||
|
"com.apple.ScriptEditor2",
|
||||||
|
"com.apple.Automator",
|
||||||
|
"com.apple.shortcuts",
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trading / crypto platforms — granted at tier `"read"` so the agent can see
|
||||||
|
* balances and prices but can't click into an order, transfer, or IB chat.
|
||||||
|
* Bundle IDs populated from Homebrew cask `uninstall.quit` stanzas as they're
|
||||||
|
* verified; the name-substring fallback below is the primary check. Bloomberg
|
||||||
|
* Terminal has no native macOS build per their FAQ (web/Citrix only).
|
||||||
|
*
|
||||||
|
* Budgeting/accounting apps (Quicken, YNAB, QuickBooks, etc.) are NOT listed
|
||||||
|
* here — they default to tier `"full"`. The risk model for brokerage/crypto
|
||||||
|
* (a stray click can execute a trade) doesn't apply to budgeting apps; the
|
||||||
|
* Cowork system prompt carries the soft instruction to never execute trades
|
||||||
|
* or transfer money on the user's behalf.
|
||||||
|
*/
|
||||||
|
const TRADING_BUNDLE_IDS: ReadonlySet<string> = new Set([
|
||||||
|
// Verified via Homebrew quit/zap stanzas + mdls + electron-builder source.
|
||||||
|
// Trading
|
||||||
|
"com.webull.desktop.v1", // Webull (direct download, Qt)
|
||||||
|
"com.webull.trade.mac.v1", // Webull (Mac App Store)
|
||||||
|
"com.tastytrade.desktop",
|
||||||
|
"com.tradingview.tradingviewapp.desktop",
|
||||||
|
"com.fidelity.activetrader", // Fidelity Trader+ (new)
|
||||||
|
"com.fmr.activetrader", // Fidelity Active Trader Pro (legacy)
|
||||||
|
// Interactive Brokers TWS — install4j wrapper; Homebrew quit stanza is
|
||||||
|
// authoritative for this exact value but install4j IDs can drift across
|
||||||
|
// major versions — name-substring "trader workstation" is the fallback.
|
||||||
|
"com.install4j.5889-6375-8446-2021",
|
||||||
|
// Crypto
|
||||||
|
"com.binance.BinanceDesktop",
|
||||||
|
"com.electron.exodus",
|
||||||
|
// Electrum uses PyInstaller with bundle_identifier=None → defaults to
|
||||||
|
// org.pythonmac.unspecified.<AppName>. Confirmed in spesmilo/electrum
|
||||||
|
// source + Homebrew zap. IntuneBrew's "org.electrum.electrum" is a fork.
|
||||||
|
"org.pythonmac.unspecified.Electrum",
|
||||||
|
"com.ledger.live",
|
||||||
|
"io.trezor.TrezorSuite",
|
||||||
|
// No native macOS app (name-substring only): Schwab, E*TRADE, TradeStation,
|
||||||
|
// Robinhood, NinjaTrader, Coinbase, Kraken, Bloomberg. thinkorswim
|
||||||
|
// install4j ID drifts per-install — substring safer.
|
||||||
|
]);
|
||||||
|
|
||||||
|
// ─── Policy-deny (not a tier — cannot be granted at all) ─────────────────
|
||||||
|
//
|
||||||
|
// Streaming / ebook / music apps and a handful of publisher apps. These
|
||||||
|
// are auto-denied before the approval dialog — no tier can be granted.
|
||||||
|
// Rationale is copyright / content-control (the agent has no legitimate
|
||||||
|
// need to screenshot Netflix or click Play on Spotify).
|
||||||
|
//
|
||||||
|
// Sourced from the ACP CU-apps blocklist xlsx ("Full block" tab). See
|
||||||
|
// /tmp/extract_cu_blocklist.py for the extraction script.
|
||||||
|
|
||||||
|
const POLICY_DENIED_BUNDLE_IDS: ReadonlySet<string> = new Set([
|
||||||
|
// Verified via Homebrew quit/zap + mdls /System/Applications + IntuneBrew.
|
||||||
|
// Apple built-ins
|
||||||
|
"com.apple.TV",
|
||||||
|
"com.apple.Music",
|
||||||
|
"com.apple.iBooksX",
|
||||||
|
"com.apple.podcasts",
|
||||||
|
// Music
|
||||||
|
"com.spotify.client",
|
||||||
|
"com.amazon.music",
|
||||||
|
"com.tidal.desktop",
|
||||||
|
"com.deezer.deezer-desktop",
|
||||||
|
"com.pandora.desktop",
|
||||||
|
"com.electron.pocket-casts", // direct-download Electron wrapper
|
||||||
|
"au.com.shiftyjelly.PocketCasts", // Mac App Store
|
||||||
|
// Video
|
||||||
|
"tv.plex.desktop",
|
||||||
|
"tv.plex.htpc",
|
||||||
|
"tv.plex.plexamp",
|
||||||
|
"com.amazon.aiv.AIVApp", // Prime Video (iOS-on-Apple-Silicon)
|
||||||
|
// Ebooks
|
||||||
|
"net.kovidgoyal.calibre",
|
||||||
|
"com.amazon.Kindle", // legacy desktop, discontinued
|
||||||
|
"com.amazon.Lassen", // current Mac App Store (iOS-on-Mac)
|
||||||
|
"com.kobo.desktop.Kobo",
|
||||||
|
// No native macOS app (name-substring only): Netflix, Disney+, Hulu,
|
||||||
|
// HBO Max, Peacock, Paramount+, YouTube, Crunchyroll, Tubi, Vudu,
|
||||||
|
// Audible, Reddit, NYTimes. Their iOS apps don't opt into iPad-on-Mac.
|
||||||
|
]);
|
||||||
|
|
||||||
|
const POLICY_DENIED_NAME_SUBSTRINGS: readonly string[] = [
|
||||||
|
// Video streaming
|
||||||
|
"netflix",
|
||||||
|
"disney+",
|
||||||
|
"hulu",
|
||||||
|
"prime video",
|
||||||
|
"apple tv",
|
||||||
|
"peacock",
|
||||||
|
"paramount+",
|
||||||
|
// "plex" is too generic — would match "Perplexity". Covered by
|
||||||
|
// tv.plex.* bundle IDs on macOS.
|
||||||
|
"tubi",
|
||||||
|
"crunchyroll",
|
||||||
|
"vudu",
|
||||||
|
// E-readers / audiobooks
|
||||||
|
"kindle",
|
||||||
|
"apple books",
|
||||||
|
"kobo",
|
||||||
|
"play books",
|
||||||
|
"calibre",
|
||||||
|
"libby",
|
||||||
|
"readium",
|
||||||
|
"audible",
|
||||||
|
"libro.fm",
|
||||||
|
"speechify",
|
||||||
|
// Music
|
||||||
|
"spotify",
|
||||||
|
"apple music",
|
||||||
|
"amazon music",
|
||||||
|
"youtube music",
|
||||||
|
"tidal",
|
||||||
|
"deezer",
|
||||||
|
"pandora",
|
||||||
|
"pocket casts",
|
||||||
|
// Publisher / social apps (from the same blocklist tab)
|
||||||
|
"naver",
|
||||||
|
"reddit",
|
||||||
|
"sony music",
|
||||||
|
"vegas pro",
|
||||||
|
"pitchfork",
|
||||||
|
"economist",
|
||||||
|
"nytimes",
|
||||||
|
// Skipped (too generic for substring matching — need bundle ID):
|
||||||
|
// HBO Max / Max, YouTube (non-Music), Nook, Sony Catalyst, Wired
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Policy-level auto-deny. Unlike `userDeniedBundleIds` (per-user Settings
|
||||||
|
* page), this is baked into the build. `buildAccessRequest` strips these
|
||||||
|
* before the approval dialog with "blocked by policy" guidance; the agent
|
||||||
|
* is told to not retry.
|
||||||
|
*/
|
||||||
|
export function isPolicyDenied(
|
||||||
|
bundleId: string | undefined,
|
||||||
|
displayName: string,
|
||||||
|
): boolean {
|
||||||
|
if (bundleId && POLICY_DENIED_BUNDLE_IDS.has(bundleId)) return true;
|
||||||
|
const lower = displayName.toLowerCase();
|
||||||
|
for (const sub of POLICY_DENIED_NAME_SUBSTRINGS) {
|
||||||
|
if (lower.includes(sub)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDeniedCategory(bundleId: string): DeniedCategory | null {
|
||||||
|
if (BROWSER_BUNDLE_IDS.has(bundleId)) return "browser";
|
||||||
|
if (TERMINAL_BUNDLE_IDS.has(bundleId)) return "terminal";
|
||||||
|
if (TRADING_BUNDLE_IDS.has(bundleId)) return "trading";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Display-name fallback (cross-platform) ──────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lowercase substrings checked against the requested display name. Catches:
|
||||||
|
* - Unresolved requests (app not installed, Spotlight miss)
|
||||||
|
* - Future Windows/Linux support where bundleId is meaningless
|
||||||
|
*
|
||||||
|
* Matched via `.includes()` on `name.toLowerCase()`. Entries are ordered
|
||||||
|
* by specificity (more-specific first is irrelevant since we return on
|
||||||
|
* first match, but groupings are by category for readability).
|
||||||
|
*/
|
||||||
|
const BROWSER_NAME_SUBSTRINGS: readonly string[] = [
|
||||||
|
"safari",
|
||||||
|
"chrome",
|
||||||
|
"firefox",
|
||||||
|
"microsoft edge",
|
||||||
|
"brave",
|
||||||
|
"opera",
|
||||||
|
"vivaldi",
|
||||||
|
"chromium",
|
||||||
|
// Arc/Dia: the canonical display name is just "Arc"/"Dia" — too short for
|
||||||
|
// substring matching (false-positives: "Arcade", "Diagram"). Covered by
|
||||||
|
// bundle ID on macOS. The "... browser" entries below catch natural-language
|
||||||
|
// phrasings ("the arc browser") but NOT the canonical short name.
|
||||||
|
"arc browser",
|
||||||
|
"tor browser",
|
||||||
|
"duckduckgo",
|
||||||
|
"yandex",
|
||||||
|
"orion browser",
|
||||||
|
// Agentic / AI browsers
|
||||||
|
"comet", // Perplexity's browser — "Comet" substring risks false positives
|
||||||
|
// but leaving for now; "comet" in an app name is rare
|
||||||
|
"sigmaos",
|
||||||
|
"dia browser",
|
||||||
|
];
|
||||||
|
|
||||||
|
const TERMINAL_NAME_SUBSTRINGS: readonly string[] = [
|
||||||
|
// macOS / cross-platform terminals
|
||||||
|
"terminal", // catches Terminal, Windows Terminal (NOT iTerm — separate entry)
|
||||||
|
"iterm",
|
||||||
|
"wezterm",
|
||||||
|
"alacritty",
|
||||||
|
"kitty",
|
||||||
|
"ghostty",
|
||||||
|
"tabby",
|
||||||
|
"termius",
|
||||||
|
// AppleScript runners — see bundle-ID comment above. "shortcuts" is too
|
||||||
|
// generic for substring matching (many apps have "shortcuts" in the name);
|
||||||
|
// covered by bundle ID only, like warp/hyper.
|
||||||
|
"script editor",
|
||||||
|
"automator",
|
||||||
|
// NOTE: "warp" and "hyper" are too generic for substring matching —
|
||||||
|
// they'd false-positive on "Warpaint" or "Hyperion". Covered by bundle ID
|
||||||
|
// (dev.warp.Warp-Stable, co.zeit.hyper) for macOS; Windows exe-name
|
||||||
|
// matching can be added when Windows CU ships.
|
||||||
|
// Windows shells (activate when the darwin gate lifts)
|
||||||
|
"powershell",
|
||||||
|
"cmd.exe",
|
||||||
|
"command prompt",
|
||||||
|
"git bash",
|
||||||
|
"conemu",
|
||||||
|
"cmder",
|
||||||
|
// IDEs (VS Code family)
|
||||||
|
"visual studio code",
|
||||||
|
"visual studio", // catches VS for Mac + Windows
|
||||||
|
"vscode",
|
||||||
|
"vs code",
|
||||||
|
"vscodium",
|
||||||
|
"cursor", // Cursor IDE — "cursor" is generic but IDE is the only common app
|
||||||
|
"windsurf",
|
||||||
|
// Zed: display name is just "Zed" — too short for substring matching
|
||||||
|
// (false-positives). Covered by bundle ID (dev.zed.Zed) on macOS.
|
||||||
|
// IDEs (JetBrains family)
|
||||||
|
"intellij",
|
||||||
|
"pycharm",
|
||||||
|
"webstorm",
|
||||||
|
"clion",
|
||||||
|
"goland",
|
||||||
|
"rubymine",
|
||||||
|
"phpstorm",
|
||||||
|
"datagrip",
|
||||||
|
"rider",
|
||||||
|
"appcode",
|
||||||
|
"rustrover",
|
||||||
|
"fleet",
|
||||||
|
"android studio",
|
||||||
|
// Other IDEs
|
||||||
|
"sublime text",
|
||||||
|
"macvim",
|
||||||
|
"neovim",
|
||||||
|
"emacs",
|
||||||
|
"xcode",
|
||||||
|
"eclipse",
|
||||||
|
"netbeans",
|
||||||
|
];
|
||||||
|
|
||||||
|
const TRADING_NAME_SUBSTRINGS: readonly string[] = [
|
||||||
|
// Trading — brokerage apps. Sourced from the ACP CU-apps blocklist xlsx
|
||||||
|
// ("Read Only" tab). Name-substring safe for proper nouns below; generic
|
||||||
|
// names (IG, Delta, HTX) are skipped and need bundle-ID matching once
|
||||||
|
// verified.
|
||||||
|
"bloomberg",
|
||||||
|
"ameritrade",
|
||||||
|
"thinkorswim",
|
||||||
|
"schwab",
|
||||||
|
"fidelity",
|
||||||
|
"e*trade",
|
||||||
|
"interactive brokers",
|
||||||
|
"trader workstation", // Interactive Brokers TWS
|
||||||
|
"tradestation",
|
||||||
|
"webull",
|
||||||
|
"robinhood",
|
||||||
|
"tastytrade",
|
||||||
|
"ninjatrader",
|
||||||
|
"tradingview",
|
||||||
|
"moomoo",
|
||||||
|
"tradezero",
|
||||||
|
"prorealtime",
|
||||||
|
"plus500",
|
||||||
|
"saxotrader",
|
||||||
|
"oanda",
|
||||||
|
"metatrader",
|
||||||
|
"forex.com",
|
||||||
|
"avaoptions",
|
||||||
|
"ctrader",
|
||||||
|
"jforex",
|
||||||
|
"iq option",
|
||||||
|
"olymp trade",
|
||||||
|
"binomo",
|
||||||
|
"pocket option",
|
||||||
|
"raceoption",
|
||||||
|
"expertoption",
|
||||||
|
"quotex",
|
||||||
|
"naga",
|
||||||
|
"morgan stanley",
|
||||||
|
"ubs neo",
|
||||||
|
"eikon", // Thomson Reuters / LSEG Workspace
|
||||||
|
// Crypto — exchanges, wallets, portfolio trackers
|
||||||
|
"coinbase",
|
||||||
|
"kraken",
|
||||||
|
"binance",
|
||||||
|
"okx",
|
||||||
|
"bybit",
|
||||||
|
// "gate.io" is too generic — the ".io" TLD suffix is common in app names
|
||||||
|
// (e.g., "Draw.io"). Needs bundle-ID matching once verified.
|
||||||
|
"phemex",
|
||||||
|
"stormgain",
|
||||||
|
"crypto.com",
|
||||||
|
// "exodus" is too generic — it's a common noun and would match unrelated
|
||||||
|
// apps/games. Needs bundle-ID matching once verified.
|
||||||
|
"electrum",
|
||||||
|
"ledger live",
|
||||||
|
"trezor",
|
||||||
|
"guarda",
|
||||||
|
"atomic wallet",
|
||||||
|
"bitpay",
|
||||||
|
"bisq",
|
||||||
|
"koinly",
|
||||||
|
"cointracker",
|
||||||
|
"blockfi",
|
||||||
|
"stripe cli",
|
||||||
|
// Crypto games / metaverse (same trade-execution risk model)
|
||||||
|
"decentraland",
|
||||||
|
"axie infinity",
|
||||||
|
"gods unchained",
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display-name substring match. Called when bundle-ID resolution returned
|
||||||
|
* nothing (`resolved === undefined`) or when no bundle-ID deny-list entry
|
||||||
|
* matched. Returns the category for the first matching substring, or null.
|
||||||
|
*
|
||||||
|
* Case-insensitive, substring — so `"Google Chrome"`, `"chrome"`, and
|
||||||
|
* `"Chrome Canary"` all match the `"chrome"` entry.
|
||||||
|
*/
|
||||||
|
export function getDeniedCategoryByDisplayName(
|
||||||
|
name: string,
|
||||||
|
): DeniedCategory | null {
|
||||||
|
const lower = name.toLowerCase();
|
||||||
|
// Trading first — proper-noun-only set, most specific. "Bloomberg Terminal"
|
||||||
|
// contains "terminal" and would miscategorize if TERMINAL_NAME_SUBSTRINGS
|
||||||
|
// ran first.
|
||||||
|
for (const sub of TRADING_NAME_SUBSTRINGS) {
|
||||||
|
if (lower.includes(sub)) return "trading";
|
||||||
|
}
|
||||||
|
for (const sub of BROWSER_NAME_SUBSTRINGS) {
|
||||||
|
if (lower.includes(sub)) return "browser";
|
||||||
|
}
|
||||||
|
for (const sub of TERMINAL_NAME_SUBSTRINGS) {
|
||||||
|
if (lower.includes(sub)) return "terminal";
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combined check — bundle ID first (exact, fast), then display-name
|
||||||
|
* fallback. This is the function tool-call handlers should use.
|
||||||
|
*
|
||||||
|
* `bundleId` may be undefined (unresolved request — model asked for an app
|
||||||
|
* that isn't installed or Spotlight didn't find). In that case only the
|
||||||
|
* display-name check runs.
|
||||||
|
*/
|
||||||
|
export function getDeniedCategoryForApp(
|
||||||
|
bundleId: string | undefined,
|
||||||
|
displayName: string,
|
||||||
|
): DeniedCategory | null {
|
||||||
|
if (bundleId) {
|
||||||
|
const byId = getDeniedCategory(bundleId);
|
||||||
|
if (byId) return byId;
|
||||||
|
}
|
||||||
|
return getDeniedCategoryByDisplayName(displayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default tier for an app at grant time. Wraps `getDeniedCategoryForApp` +
|
||||||
|
* `categoryToTier`. Browsers → `"read"`, terminals/IDEs → `"click"`,
|
||||||
|
* everything else → `"full"`.
|
||||||
|
*
|
||||||
|
* Called by `buildAccessRequest` to populate `ResolvedAppRequest.proposedTier`
|
||||||
|
* before the approval dialog shows.
|
||||||
|
*/
|
||||||
|
export function getDefaultTierForApp(
|
||||||
|
bundleId: string | undefined,
|
||||||
|
displayName: string,
|
||||||
|
): "read" | "click" | "full" {
|
||||||
|
return categoryToTier(getDeniedCategoryForApp(bundleId, displayName));
|
||||||
|
}
|
||||||
|
|
||||||
|
export const _test = {
|
||||||
|
BROWSER_BUNDLE_IDS,
|
||||||
|
TERMINAL_BUNDLE_IDS,
|
||||||
|
TRADING_BUNDLE_IDS,
|
||||||
|
POLICY_DENIED_BUNDLE_IDS,
|
||||||
|
BROWSER_NAME_SUBSTRINGS,
|
||||||
|
TERMINAL_NAME_SUBSTRINGS,
|
||||||
|
TRADING_NAME_SUBSTRINGS,
|
||||||
|
POLICY_DENIED_NAME_SUBSTRINGS,
|
||||||
|
};
|
||||||
100
src/vendor/computer-use-mcp/executor.ts
vendored
Normal file
100
src/vendor/computer-use-mcp/executor.ts
vendored
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
export interface DisplayGeometry {
|
||||||
|
id?: number
|
||||||
|
displayId?: number
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
scaleFactor: number
|
||||||
|
originX: number
|
||||||
|
originY: number
|
||||||
|
isPrimary?: boolean
|
||||||
|
name?: string
|
||||||
|
label?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScreenshotResult {
|
||||||
|
base64: string
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
displayWidth: number
|
||||||
|
displayHeight: number
|
||||||
|
displayId?: number
|
||||||
|
originX: number
|
||||||
|
originY: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FrontmostApp {
|
||||||
|
bundleId: string
|
||||||
|
displayName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RunningApp {
|
||||||
|
bundleId: string
|
||||||
|
displayName: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InstalledApp {
|
||||||
|
bundleId: string
|
||||||
|
displayName: string
|
||||||
|
path: string
|
||||||
|
iconDataUrl?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResolvePrepareCaptureResult extends ScreenshotResult {
|
||||||
|
hidden: string[]
|
||||||
|
display: DisplayGeometry
|
||||||
|
resolvedDisplayId?: number
|
||||||
|
captureError?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ComputerExecutor {
|
||||||
|
capabilities: {
|
||||||
|
screenshotFiltering: 'native' | 'none'
|
||||||
|
platform: 'darwin' | 'win32'
|
||||||
|
hostBundleId: string
|
||||||
|
teachMode?: boolean
|
||||||
|
}
|
||||||
|
prepareForAction(allowlistBundleIds: string[], displayId?: number): Promise<string[]>
|
||||||
|
previewHideSet(allowlistBundleIds: string[], displayId?: number): Promise<Array<{ bundleId: string; displayName: string }>>
|
||||||
|
getDisplaySize(displayId?: number): Promise<DisplayGeometry>
|
||||||
|
listDisplays(): Promise<DisplayGeometry[]>
|
||||||
|
findWindowDisplays(bundleIds: string[]): Promise<Array<{ bundleId: string; displayIds: number[] }>>
|
||||||
|
resolvePrepareCapture(opts: {
|
||||||
|
allowedBundleIds: string[]
|
||||||
|
preferredDisplayId?: number
|
||||||
|
autoResolve: boolean
|
||||||
|
doHide?: boolean
|
||||||
|
}): Promise<ResolvePrepareCaptureResult>
|
||||||
|
screenshot(opts: { allowedBundleIds: string[]; displayId?: number }): Promise<ScreenshotResult>
|
||||||
|
zoom(
|
||||||
|
regionLogical: { x: number; y: number; w: number; h: number },
|
||||||
|
allowedBundleIds: string[],
|
||||||
|
displayId?: number,
|
||||||
|
): Promise<{ base64: string; width: number; height: number }>
|
||||||
|
key(keySequence: string, repeat?: number): Promise<void>
|
||||||
|
holdKey(keyNames: string[], durationMs: number): Promise<void>
|
||||||
|
type(text: string, opts: { viaClipboard: boolean }): Promise<void>
|
||||||
|
click(
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
button: 'left' | 'right' | 'middle',
|
||||||
|
count: 1 | 2 | 3,
|
||||||
|
modifiers?: string[],
|
||||||
|
): Promise<void>
|
||||||
|
drag(
|
||||||
|
from: { x: number; y: number } | undefined,
|
||||||
|
to: { x: number; y: number },
|
||||||
|
): Promise<void>
|
||||||
|
moveMouse(x: number, y: number): Promise<void>
|
||||||
|
scroll(x: number, y: number, deltaX: number, deltaY: number): Promise<void>
|
||||||
|
mouseDown(): Promise<void>
|
||||||
|
mouseUp(): Promise<void>
|
||||||
|
getCursorPosition(): Promise<{ x: number; y: number }>
|
||||||
|
getFrontmostApp(): Promise<FrontmostApp | null>
|
||||||
|
appUnderPoint(x: number, y: number): Promise<{ bundleId: string; displayName: string } | null>
|
||||||
|
listInstalledApps(): Promise<InstalledApp[]>
|
||||||
|
getAppIcon?(path: string): Promise<string | undefined>
|
||||||
|
listRunningApps(): Promise<RunningApp[]>
|
||||||
|
openApp(bundleId: string): Promise<void>
|
||||||
|
readClipboard(): Promise<string>
|
||||||
|
writeClipboard(text: string): Promise<void>
|
||||||
|
}
|
||||||
108
src/vendor/computer-use-mcp/imageResize.ts
vendored
Normal file
108
src/vendor/computer-use-mcp/imageResize.ts
vendored
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
/**
|
||||||
|
* Port of the API's image transcoder target-size algorithm. Pre-sizing
|
||||||
|
* screenshots to this function's output means the API's early-return fires
|
||||||
|
* (tokens ≤ max) and the image is NOT resized server-side — so the model
|
||||||
|
* sees exactly the dimensions in `ScreenshotResult.width/height` and
|
||||||
|
* `scaleCoord` stays coherent.
|
||||||
|
*
|
||||||
|
* Rust reference: api/api/image_transcoder/rust_transcoder/src/utils/resize.rs
|
||||||
|
* Sibling TS port: apps/claude-browser-use/src/utils/imageResize.ts (identical
|
||||||
|
* algorithm, lives in the Chrome extension tree — not a shared package).
|
||||||
|
*
|
||||||
|
* See COORDINATES.md for why this matters for click accuracy.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ResizeParams {
|
||||||
|
pxPerToken: number;
|
||||||
|
maxTargetPx: number;
|
||||||
|
maxTargetTokens: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Production defaults — match `resize.rs:160-164` and Chrome's
|
||||||
|
* `CDPService.ts:638-642`. Vision encoder uses 28px tiles; 1568 is both
|
||||||
|
* the long-edge cap (56 tiles) AND the token budget.
|
||||||
|
*/
|
||||||
|
export const API_RESIZE_PARAMS: ResizeParams = {
|
||||||
|
pxPerToken: 28,
|
||||||
|
maxTargetPx: 1568,
|
||||||
|
maxTargetTokens: 1568,
|
||||||
|
};
|
||||||
|
|
||||||
|
/** ceil(px / pxPerToken). Matches resize.rs:74-76 (which uses integer ceil-div). */
|
||||||
|
export function nTokensForPx(px: number, pxPerToken: number): number {
|
||||||
|
return Math.floor((px - 1) / pxPerToken) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function nTokensForImg(
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
pxPerToken: number,
|
||||||
|
): number {
|
||||||
|
return nTokensForPx(width, pxPerToken) * nTokensForPx(height, pxPerToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binary-search along the width dimension for the largest image that:
|
||||||
|
* - preserves the input aspect ratio
|
||||||
|
* - has long edge ≤ maxTargetPx
|
||||||
|
* - has ceil(w/pxPerToken) × ceil(h/pxPerToken) ≤ maxTargetTokens
|
||||||
|
*
|
||||||
|
* Returns [width, height]. No-op if input already satisfies all three.
|
||||||
|
*
|
||||||
|
* The long-edge constraint alone (what we used to use) is insufficient on
|
||||||
|
* squarer-than-16:9 displays: 1568×1014 (MBP 16" AR) is 56×37 = 2072 tokens,
|
||||||
|
* over budget, and gets server-resized to 1372×887 — model then clicks in
|
||||||
|
* 1372-space but scaleCoord assumed 1568-space → ~14% coord error.
|
||||||
|
*
|
||||||
|
* Matches resize.rs:91-155 exactly (verified against its test vectors).
|
||||||
|
*/
|
||||||
|
export function targetImageSize(
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
params: ResizeParams,
|
||||||
|
): [number, number] {
|
||||||
|
const { pxPerToken, maxTargetPx, maxTargetTokens } = params;
|
||||||
|
|
||||||
|
if (
|
||||||
|
width <= maxTargetPx &&
|
||||||
|
height <= maxTargetPx &&
|
||||||
|
nTokensForImg(width, height, pxPerToken) <= maxTargetTokens
|
||||||
|
) {
|
||||||
|
return [width, height];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize to landscape for the search; transpose result back.
|
||||||
|
if (height > width) {
|
||||||
|
const [w, h] = targetImageSize(height, width, params);
|
||||||
|
return [h, w];
|
||||||
|
}
|
||||||
|
|
||||||
|
const aspectRatio = width / height;
|
||||||
|
|
||||||
|
// Loop invariant: lowerBoundWidth is always valid, upperBoundWidth is
|
||||||
|
// always invalid. ~12 iterations for a 4000px image.
|
||||||
|
let upperBoundWidth = width;
|
||||||
|
let lowerBoundWidth = 1;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
if (lowerBoundWidth + 1 === upperBoundWidth) {
|
||||||
|
return [
|
||||||
|
lowerBoundWidth,
|
||||||
|
Math.max(Math.round(lowerBoundWidth / aspectRatio), 1),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const middleWidth = Math.floor((lowerBoundWidth + upperBoundWidth) / 2);
|
||||||
|
const middleHeight = Math.max(Math.round(middleWidth / aspectRatio), 1);
|
||||||
|
|
||||||
|
if (
|
||||||
|
middleWidth <= maxTargetPx &&
|
||||||
|
nTokensForImg(middleWidth, middleHeight, pxPerToken) <= maxTargetTokens
|
||||||
|
) {
|
||||||
|
lowerBoundWidth = middleWidth;
|
||||||
|
} else {
|
||||||
|
upperBoundWidth = middleWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
69
src/vendor/computer-use-mcp/index.ts
vendored
Normal file
69
src/vendor/computer-use-mcp/index.ts
vendored
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
export type {
|
||||||
|
ComputerExecutor,
|
||||||
|
DisplayGeometry,
|
||||||
|
FrontmostApp,
|
||||||
|
InstalledApp,
|
||||||
|
ResolvePrepareCaptureResult,
|
||||||
|
RunningApp,
|
||||||
|
ScreenshotResult,
|
||||||
|
} from "./executor.js";
|
||||||
|
|
||||||
|
export type {
|
||||||
|
AppGrant,
|
||||||
|
CuAppPermTier,
|
||||||
|
ComputerUseHostAdapter,
|
||||||
|
ComputerUseOverrides,
|
||||||
|
ComputerUseSessionContext,
|
||||||
|
CoordinateMode,
|
||||||
|
CuGrantFlags,
|
||||||
|
CuPermissionRequest,
|
||||||
|
CuPermissionResponse,
|
||||||
|
CuSubGates,
|
||||||
|
CuTeachPermissionRequest,
|
||||||
|
Logger,
|
||||||
|
ResolvedAppRequest,
|
||||||
|
ScreenshotDims,
|
||||||
|
TeachStepRequest,
|
||||||
|
TeachStepResult,
|
||||||
|
} from "./types.js";
|
||||||
|
|
||||||
|
export { DEFAULT_GRANT_FLAGS } from "./types.js";
|
||||||
|
|
||||||
|
export {
|
||||||
|
SENTINEL_BUNDLE_IDS,
|
||||||
|
getSentinelCategory,
|
||||||
|
} from "./sentinelApps.js";
|
||||||
|
export type { SentinelCategory } from "./sentinelApps.js";
|
||||||
|
|
||||||
|
export {
|
||||||
|
categoryToTier,
|
||||||
|
getDefaultTierForApp,
|
||||||
|
getDeniedCategory,
|
||||||
|
getDeniedCategoryByDisplayName,
|
||||||
|
getDeniedCategoryForApp,
|
||||||
|
isPolicyDenied,
|
||||||
|
} from "./deniedApps.js";
|
||||||
|
export type { DeniedCategory } from "./deniedApps.js";
|
||||||
|
|
||||||
|
export { isSystemKeyCombo, normalizeKeySequence } from "./keyBlocklist.js";
|
||||||
|
|
||||||
|
export { ALL_SUB_GATES_OFF, ALL_SUB_GATES_ON } from "./subGates.js";
|
||||||
|
|
||||||
|
export { API_RESIZE_PARAMS, targetImageSize } from "./imageResize.js";
|
||||||
|
export type { ResizeParams } from "./imageResize.js";
|
||||||
|
|
||||||
|
export { defersLockAcquire, handleToolCall } from "./toolCalls.js";
|
||||||
|
export type {
|
||||||
|
CuCallTelemetry,
|
||||||
|
CuCallToolResult,
|
||||||
|
CuErrorKind,
|
||||||
|
} from "./toolCalls.js";
|
||||||
|
|
||||||
|
export { bindSessionContext, createComputerUseMcpServer } from "./mcpServer.js";
|
||||||
|
export { buildComputerUseTools } from "./tools.js";
|
||||||
|
|
||||||
|
export {
|
||||||
|
comparePixelAtLocation,
|
||||||
|
validateClickTarget,
|
||||||
|
} from "./pixelCompare.js";
|
||||||
|
export type { CropRawPatchFn, PixelCompareResult } from "./pixelCompare.js";
|
||||||
153
src/vendor/computer-use-mcp/keyBlocklist.ts
vendored
Normal file
153
src/vendor/computer-use-mcp/keyBlocklist.ts
vendored
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
/**
|
||||||
|
* Key combos that cross app boundaries or terminate processes. Gated behind
|
||||||
|
* the `systemKeyCombos` grant flag. When that flag is off, the `key` tool
|
||||||
|
* rejects these and returns a tool error telling the model to request the
|
||||||
|
* flag; all other combos work normally.
|
||||||
|
*
|
||||||
|
* Matching is canonicalized: every modifier alias the Rust executor accepts
|
||||||
|
* collapses to one canonical name. Without this, `command+q` / `meta+q` /
|
||||||
|
* `cmd+alt+escape` bypass the gate — see keyBlocklist.test.ts for the three
|
||||||
|
* bypass forms and the Rust parity check that catches future alias drift.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Every modifier alias enigo_wrap.rs accepts (two copies: :351-359, :564-572),
|
||||||
|
* mapped to one canonical per Key:: variant. Left/right variants collapse —
|
||||||
|
* the blocklist doesn't distinguish which Ctrl.
|
||||||
|
*
|
||||||
|
* Canonical names are Rust's own variant names lowercased. Blocklist entries
|
||||||
|
* below use ONLY these. "meta" reads odd for Cmd+Q but it's honest: Rust
|
||||||
|
* sends Key::Meta, which is Cmd on darwin and Win on win32.
|
||||||
|
*/
|
||||||
|
const CANONICAL_MODIFIER: Readonly<Record<string, string>> = {
|
||||||
|
// Key::Meta — "meta"|"super"|"command"|"cmd"|"windows"|"win"
|
||||||
|
meta: "meta",
|
||||||
|
super: "meta",
|
||||||
|
command: "meta",
|
||||||
|
cmd: "meta",
|
||||||
|
windows: "meta",
|
||||||
|
win: "meta",
|
||||||
|
// Key::Control + LControl + RControl
|
||||||
|
ctrl: "ctrl",
|
||||||
|
control: "ctrl",
|
||||||
|
lctrl: "ctrl",
|
||||||
|
lcontrol: "ctrl",
|
||||||
|
rctrl: "ctrl",
|
||||||
|
rcontrol: "ctrl",
|
||||||
|
// Key::Shift + LShift + RShift
|
||||||
|
shift: "shift",
|
||||||
|
lshift: "shift",
|
||||||
|
rshift: "shift",
|
||||||
|
// Key::Alt and Key::Option — distinct Rust variants but same keycode on
|
||||||
|
// darwin (kVK_Option). Collapse: cmd+alt+escape and cmd+option+escape
|
||||||
|
// both Force Quit.
|
||||||
|
alt: "alt",
|
||||||
|
option: "alt",
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Sort order for canonicals. ctrl < alt < shift < meta. */
|
||||||
|
const MODIFIER_ORDER = ["ctrl", "alt", "shift", "meta"];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Canonical-form entries only. Every modifier must be a CANONICAL_MODIFIER
|
||||||
|
* *value* (not key), modifiers must be in MODIFIER_ORDER, non-modifier last.
|
||||||
|
* The self-consistency test enforces this.
|
||||||
|
*/
|
||||||
|
const BLOCKED_DARWIN = new Set([
|
||||||
|
"meta+q", // Cmd+Q — quit frontmost app
|
||||||
|
"shift+meta+q", // Cmd+Shift+Q — log out
|
||||||
|
"alt+meta+escape", // Cmd+Option+Esc — Force Quit dialog
|
||||||
|
"meta+tab", // Cmd+Tab — app switcher
|
||||||
|
"meta+space", // Cmd+Space — Spotlight
|
||||||
|
"ctrl+meta+q", // Ctrl+Cmd+Q — lock screen
|
||||||
|
]);
|
||||||
|
|
||||||
|
const BLOCKED_WIN32 = new Set([
|
||||||
|
"ctrl+alt+delete", // Secure Attention Sequence
|
||||||
|
"alt+f4", // close window
|
||||||
|
"alt+tab", // window switcher
|
||||||
|
"meta+l", // Win+L — lock
|
||||||
|
"meta+d", // Win+D — show desktop
|
||||||
|
]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Partition into sorted-canonical modifiers and non-modifier keys.
|
||||||
|
* Shared by normalizeKeySequence (join for display) and isSystemKeyCombo
|
||||||
|
* (check mods+each-key to catch the cmd+q+a suffix bypass).
|
||||||
|
*/
|
||||||
|
function partitionKeys(seq: string): { mods: string[]; keys: string[] } {
|
||||||
|
const parts = seq
|
||||||
|
.toLowerCase()
|
||||||
|
.split("+")
|
||||||
|
.map((p) => p.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
const mods: string[] = [];
|
||||||
|
const keys: string[] = [];
|
||||||
|
for (const p of parts) {
|
||||||
|
const canonical = CANONICAL_MODIFIER[p];
|
||||||
|
if (canonical !== undefined) {
|
||||||
|
mods.push(canonical);
|
||||||
|
} else {
|
||||||
|
keys.push(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Dedupe: "cmd+command+q" → "meta+q", not "meta+meta+q".
|
||||||
|
const uniqueMods = [...new Set(mods)];
|
||||||
|
uniqueMods.sort(
|
||||||
|
(a, b) => MODIFIER_ORDER.indexOf(a) - MODIFIER_ORDER.indexOf(b),
|
||||||
|
);
|
||||||
|
return { mods: uniqueMods, keys };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalize "Cmd + Shift + Q" → "shift+meta+q": lowercase, trim, alias →
|
||||||
|
* canonical, dedupe, sort modifiers, non-modifiers last.
|
||||||
|
*/
|
||||||
|
export function normalizeKeySequence(seq: string): string {
|
||||||
|
const { mods, keys } = partitionKeys(seq);
|
||||||
|
return [...mods, ...keys].join("+");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the sequence would fire a blocked OS shortcut.
|
||||||
|
*
|
||||||
|
* Checks mods + EACH non-modifier key individually, not just the full
|
||||||
|
* joined string. `cmd+q+a` → Rust presses Cmd, then Q (Cmd+Q fires here),
|
||||||
|
* then A. Exact-match against "meta+q+a" misses; checking "meta+q" and
|
||||||
|
* "meta+a" separately catches the Q.
|
||||||
|
*
|
||||||
|
* Modifiers-only sequences ("cmd+shift") are checked as-is — no key to
|
||||||
|
* pair with, and no blocklist entry is modifier-only, so this is a no-op
|
||||||
|
* that falls through to false. Covers the click-modifier case where
|
||||||
|
* `left_click(text="cmd")` is legitimate.
|
||||||
|
*/
|
||||||
|
export function isSystemKeyCombo(
|
||||||
|
seq: string,
|
||||||
|
platform: "darwin" | "win32",
|
||||||
|
): boolean {
|
||||||
|
const blocklist = platform === "darwin" ? BLOCKED_DARWIN : BLOCKED_WIN32;
|
||||||
|
const { mods, keys } = partitionKeys(seq);
|
||||||
|
const prefix = mods.length > 0 ? mods.join("+") + "+" : "";
|
||||||
|
|
||||||
|
// No non-modifier keys (e.g. "cmd+shift" as click-modifiers) — check the
|
||||||
|
// whole thing. Never matches (no blocklist entry is modifier-only) but
|
||||||
|
// keeps the contract simple: every call reaches a .has().
|
||||||
|
if (keys.length === 0) {
|
||||||
|
return blocklist.has(mods.join("+"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// mods + each key. Any hit blocks the whole sequence.
|
||||||
|
for (const key of keys) {
|
||||||
|
if (blocklist.has(prefix + key)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const _test = {
|
||||||
|
CANONICAL_MODIFIER,
|
||||||
|
BLOCKED_DARWIN,
|
||||||
|
BLOCKED_WIN32,
|
||||||
|
MODIFIER_ORDER,
|
||||||
|
};
|
||||||
313
src/vendor/computer-use-mcp/mcpServer.ts
vendored
Normal file
313
src/vendor/computer-use-mcp/mcpServer.ts
vendored
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
/**
|
||||||
|
* MCP server factory + session-context binder.
|
||||||
|
*
|
||||||
|
* Two entry points:
|
||||||
|
*
|
||||||
|
* `bindSessionContext` — the wrapper closure. Takes a `ComputerUseSessionContext`
|
||||||
|
* (getters + callbacks backed by host session state), returns a dispatcher.
|
||||||
|
* Reusable by both the MCP CallTool handler here AND Cowork's
|
||||||
|
* `InternalServerDefinition.handleToolCall` (which doesn't go through MCP).
|
||||||
|
* This replaces the duplicated wrapper closures in apps/desktop/…/serverDef.ts
|
||||||
|
* and the Claude Code CLI's CU host wrapper — both did the same thing: build `ComputerUseOverrides`
|
||||||
|
* fresh from getters, call `handleToolCall`, stash screenshot, merge permissions.
|
||||||
|
*
|
||||||
|
* `createComputerUseMcpServer` — the Server object. When `context` is provided,
|
||||||
|
* the CallTool handler is real (uses `bindSessionContext`). When not, it's the
|
||||||
|
* legacy stub that returns a not-wired error. The tool-schema ListTools handler
|
||||||
|
* is the same either way.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||||
|
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
import {
|
||||||
|
CallToolRequestSchema,
|
||||||
|
ListToolsRequestSchema,
|
||||||
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
|
||||||
|
import type { ScreenshotResult } from "./executor.js";
|
||||||
|
import type { CuCallToolResult } from "./toolCalls.js";
|
||||||
|
import {
|
||||||
|
defersLockAcquire,
|
||||||
|
handleToolCall,
|
||||||
|
resetMouseButtonHeld,
|
||||||
|
} from "./toolCalls.js";
|
||||||
|
import { buildComputerUseTools } from "./tools.js";
|
||||||
|
import type {
|
||||||
|
AppGrant,
|
||||||
|
ComputerUseHostAdapter,
|
||||||
|
ComputerUseOverrides,
|
||||||
|
ComputerUseSessionContext,
|
||||||
|
CoordinateMode,
|
||||||
|
CuGrantFlags,
|
||||||
|
CuPermissionResponse,
|
||||||
|
} from "./types.js";
|
||||||
|
import { DEFAULT_GRANT_FLAGS } from "./types.js";
|
||||||
|
|
||||||
|
const DEFAULT_LOCK_HELD_MESSAGE =
|
||||||
|
"Another Claude session is currently using the computer. Wait for that " +
|
||||||
|
"session to finish, or find a non-computer-use approach.";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dedupe `granted` into `existing` on bundleId, spread truthy-only flags over
|
||||||
|
* defaults+existing. Truthy-only: a subsequent `request_access` that doesn't
|
||||||
|
* request clipboard can't revoke an earlier clipboard grant — revocation lives
|
||||||
|
* in a Settings page, not here.
|
||||||
|
*
|
||||||
|
* Same merge both hosts implemented independently today.
|
||||||
|
*/
|
||||||
|
function mergePermissionResponse(
|
||||||
|
existing: readonly AppGrant[],
|
||||||
|
existingFlags: CuGrantFlags,
|
||||||
|
response: CuPermissionResponse,
|
||||||
|
): { apps: AppGrant[]; flags: CuGrantFlags } {
|
||||||
|
const seen = new Set(existing.map((a) => a.bundleId));
|
||||||
|
const apps = [
|
||||||
|
...existing,
|
||||||
|
...response.granted.filter((g) => !seen.has(g.bundleId)),
|
||||||
|
];
|
||||||
|
const truthyFlags = Object.fromEntries(
|
||||||
|
Object.entries(response.flags).filter(([, v]) => v === true),
|
||||||
|
);
|
||||||
|
const flags: CuGrantFlags = {
|
||||||
|
...DEFAULT_GRANT_FLAGS,
|
||||||
|
...existingFlags,
|
||||||
|
...truthyFlags,
|
||||||
|
};
|
||||||
|
return { apps, flags };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind session state to a reusable dispatcher. The returned function is the
|
||||||
|
* wrapper closure: async lock gate → build overrides fresh → `handleToolCall`
|
||||||
|
* → stash screenshot → strip piggybacked fields.
|
||||||
|
*
|
||||||
|
* The last-screenshot blob is held in a closure cell here (not on `ctx`), so
|
||||||
|
* hosts don't need to guarantee `ctx` object identity across calls — they just
|
||||||
|
* need to hold onto the returned dispatcher. Cowork caches per
|
||||||
|
* `InternalServerContext` in a WeakMap; the CLI host constructs once at server creation.
|
||||||
|
*/
|
||||||
|
export function bindSessionContext(
|
||||||
|
adapter: ComputerUseHostAdapter,
|
||||||
|
coordinateMode: CoordinateMode,
|
||||||
|
ctx: ComputerUseSessionContext,
|
||||||
|
): (name: string, args: unknown) => Promise<CuCallToolResult> {
|
||||||
|
const { logger, serverName } = adapter;
|
||||||
|
|
||||||
|
// Screenshot blob persists here across calls — NOT on `ctx`. Hosts hold
|
||||||
|
// onto the returned dispatcher; that's the identity that matters.
|
||||||
|
let lastScreenshot: ScreenshotResult | undefined;
|
||||||
|
|
||||||
|
const wrapPermission = ctx.onPermissionRequest
|
||||||
|
? async (
|
||||||
|
req: Parameters<NonNullable<typeof ctx.onPermissionRequest>>[0],
|
||||||
|
signal: AbortSignal,
|
||||||
|
): Promise<CuPermissionResponse> => {
|
||||||
|
const response = await ctx.onPermissionRequest!(req, signal);
|
||||||
|
const { apps, flags } = mergePermissionResponse(
|
||||||
|
ctx.getAllowedApps(),
|
||||||
|
ctx.getGrantFlags(),
|
||||||
|
response,
|
||||||
|
);
|
||||||
|
logger.debug(
|
||||||
|
`[${serverName}] permission result: granted=${response.granted.length} denied=${response.denied.length}`,
|
||||||
|
);
|
||||||
|
ctx.onAllowedAppsChanged?.(apps, flags);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const wrapTeachPermission = ctx.onTeachPermissionRequest
|
||||||
|
? async (
|
||||||
|
req: Parameters<NonNullable<typeof ctx.onTeachPermissionRequest>>[0],
|
||||||
|
signal: AbortSignal,
|
||||||
|
): Promise<CuPermissionResponse> => {
|
||||||
|
const response = await ctx.onTeachPermissionRequest!(req, signal);
|
||||||
|
logger.debug(
|
||||||
|
`[${serverName}] teach permission result: granted=${response.granted.length} denied=${response.denied.length}`,
|
||||||
|
);
|
||||||
|
// Teach doesn't request grant flags — preserve existing.
|
||||||
|
const { apps } = mergePermissionResponse(
|
||||||
|
ctx.getAllowedApps(),
|
||||||
|
ctx.getGrantFlags(),
|
||||||
|
response,
|
||||||
|
);
|
||||||
|
ctx.onAllowedAppsChanged?.(apps, {
|
||||||
|
...DEFAULT_GRANT_FLAGS,
|
||||||
|
...ctx.getGrantFlags(),
|
||||||
|
});
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return async (name, args) => {
|
||||||
|
// ─── Async lock gate ─────────────────────────────────────────────────
|
||||||
|
// Replaces the sync Gate-3 in `handleToolCall` — we pass
|
||||||
|
// `checkCuLock: undefined` below so it no-ops. Hosts with
|
||||||
|
// cross-process locks (O_EXCL file) await the real primitive here
|
||||||
|
// instead of pre-computing + feeding a fake sync result.
|
||||||
|
if (ctx.checkCuLock) {
|
||||||
|
const lock = await ctx.checkCuLock();
|
||||||
|
if (lock.holder !== undefined && !lock.isSelf) {
|
||||||
|
const text =
|
||||||
|
ctx.formatLockHeldMessage?.(lock.holder) ?? DEFAULT_LOCK_HELD_MESSAGE;
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text }],
|
||||||
|
isError: true,
|
||||||
|
telemetry: { error_kind: "cu_lock_held" },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (lock.holder === undefined && !defersLockAcquire(name)) {
|
||||||
|
await ctx.acquireCuLock?.();
|
||||||
|
// Re-check: the awaits above yield the microtask queue, so another
|
||||||
|
// session's check+acquire can interleave with ours. Hosts where
|
||||||
|
// acquire is a no-op when already held (Cowork's CuLockManager) give
|
||||||
|
// no signal that we lost — verify we're now the holder before
|
||||||
|
// proceeding. The CLI's O_EXCL file lock would surface this as a throw from
|
||||||
|
// acquire instead; this re-check is a belt-and-suspenders for that
|
||||||
|
// path too.
|
||||||
|
const recheck = await ctx.checkCuLock();
|
||||||
|
if (recheck.holder !== undefined && !recheck.isSelf) {
|
||||||
|
const text =
|
||||||
|
ctx.formatLockHeldMessage?.(recheck.holder) ??
|
||||||
|
DEFAULT_LOCK_HELD_MESSAGE;
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text }],
|
||||||
|
isError: true,
|
||||||
|
telemetry: { error_kind: "cu_lock_held" },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Fresh holder → any prior session's mouseButtonHeld is stale.
|
||||||
|
// Mirrors what Gate-3 does on the acquire branch. After the
|
||||||
|
// re-check so we only clear module state when we actually won.
|
||||||
|
resetMouseButtonHeld();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Build overrides fresh ───────────────────────────────────────────
|
||||||
|
// Blob-first; dims-fallback with base64:"" when the closure cell is
|
||||||
|
// unset (cross-respawn). scaleCoord reads dims; pixelCompare sees "" →
|
||||||
|
// isEmpty → skip.
|
||||||
|
const dimsFallback = lastScreenshot
|
||||||
|
? undefined
|
||||||
|
: ctx.getLastScreenshotDims?.();
|
||||||
|
|
||||||
|
// Per-call AbortController for dialog dismissal. Aborted in `finally` —
|
||||||
|
// if handleToolCall finishes (MCP timeout, throw) before the user
|
||||||
|
// answers, the host's dialog handler sees the abort and tears down.
|
||||||
|
const dialogAbort = new AbortController();
|
||||||
|
|
||||||
|
const overrides: ComputerUseOverrides = {
|
||||||
|
allowedApps: [...ctx.getAllowedApps()],
|
||||||
|
grantFlags: ctx.getGrantFlags(),
|
||||||
|
userDeniedBundleIds: ctx.getUserDeniedBundleIds(),
|
||||||
|
coordinateMode,
|
||||||
|
selectedDisplayId: ctx.getSelectedDisplayId(),
|
||||||
|
displayPinnedByModel: ctx.getDisplayPinnedByModel?.(),
|
||||||
|
displayResolvedForApps: ctx.getDisplayResolvedForApps?.(),
|
||||||
|
lastScreenshot:
|
||||||
|
lastScreenshot ??
|
||||||
|
(dimsFallback ? { ...dimsFallback, base64: "" } : undefined),
|
||||||
|
onPermissionRequest: wrapPermission
|
||||||
|
? (req) => wrapPermission(req, dialogAbort.signal)
|
||||||
|
: undefined,
|
||||||
|
onTeachPermissionRequest: wrapTeachPermission
|
||||||
|
? (req) => wrapTeachPermission(req, dialogAbort.signal)
|
||||||
|
: undefined,
|
||||||
|
onAppsHidden: ctx.onAppsHidden,
|
||||||
|
getClipboardStash: ctx.getClipboardStash,
|
||||||
|
onClipboardStashChanged: ctx.onClipboardStashChanged,
|
||||||
|
onResolvedDisplayUpdated: ctx.onResolvedDisplayUpdated,
|
||||||
|
onDisplayPinned: ctx.onDisplayPinned,
|
||||||
|
onDisplayResolvedForApps: ctx.onDisplayResolvedForApps,
|
||||||
|
onTeachModeActivated: ctx.onTeachModeActivated,
|
||||||
|
onTeachStep: ctx.onTeachStep,
|
||||||
|
onTeachWorking: ctx.onTeachWorking,
|
||||||
|
getTeachModeActive: ctx.getTeachModeActive,
|
||||||
|
// Undefined → handleToolCall's sync Gate-3 no-ops. The async gate
|
||||||
|
// above already ran.
|
||||||
|
checkCuLock: undefined,
|
||||||
|
acquireCuLock: undefined,
|
||||||
|
isAborted: ctx.isAborted,
|
||||||
|
};
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
`[${serverName}] tool=${name} allowedApps=${overrides.allowedApps.length} coordMode=${coordinateMode}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// ─── Dispatch ────────────────────────────────────────────────────────
|
||||||
|
try {
|
||||||
|
const result = await handleToolCall(adapter, name, args, overrides);
|
||||||
|
|
||||||
|
if (result.screenshot) {
|
||||||
|
lastScreenshot = result.screenshot;
|
||||||
|
const { base64: _blob, ...dims } = result.screenshot;
|
||||||
|
logger.debug(`[${serverName}] screenshot dims: ${JSON.stringify(dims)}`);
|
||||||
|
ctx.onScreenshotCaptured?.(dims);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} finally {
|
||||||
|
dialogAbort.abort();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createComputerUseMcpServer(
|
||||||
|
adapter: ComputerUseHostAdapter,
|
||||||
|
coordinateMode: CoordinateMode,
|
||||||
|
context?: ComputerUseSessionContext,
|
||||||
|
): Server {
|
||||||
|
const { serverName, logger } = adapter;
|
||||||
|
|
||||||
|
const server = new Server(
|
||||||
|
{ name: serverName, version: "0.1.3" },
|
||||||
|
{ capabilities: { tools: {}, logging: {} } },
|
||||||
|
);
|
||||||
|
|
||||||
|
const tools = buildComputerUseTools(
|
||||||
|
adapter.executor.capabilities,
|
||||||
|
coordinateMode,
|
||||||
|
);
|
||||||
|
|
||||||
|
server.setRequestHandler(ListToolsRequestSchema, async () =>
|
||||||
|
adapter.isDisabled() ? { tools: [] } : { tools },
|
||||||
|
);
|
||||||
|
|
||||||
|
if (context) {
|
||||||
|
const dispatch = bindSessionContext(adapter, coordinateMode, context);
|
||||||
|
server.setRequestHandler(
|
||||||
|
CallToolRequestSchema,
|
||||||
|
async (request): Promise<CallToolResult> => {
|
||||||
|
const { screenshot: _s, telemetry: _t, ...result } = await dispatch(
|
||||||
|
request.params.name,
|
||||||
|
request.params.arguments ?? {},
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy: no context → stub handler. Reached only if something calls the
|
||||||
|
// server over MCP transport WITHOUT going through a binder (a wiring
|
||||||
|
// regression). Clear error instead of silent failure.
|
||||||
|
server.setRequestHandler(
|
||||||
|
CallToolRequestSchema,
|
||||||
|
async (request): Promise<CallToolResult> => {
|
||||||
|
logger.warn(
|
||||||
|
`[${serverName}] tool call "${request.params.name}" reached the stub handler — no session context bound. Per-session state unavailable.`,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: "This computer-use server instance is not wired to a session. Per-session app permissions are not available on this code path.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
isError: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}
|
||||||
171
src/vendor/computer-use-mcp/pixelCompare.ts
vendored
Normal file
171
src/vendor/computer-use-mcp/pixelCompare.ts
vendored
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
/**
|
||||||
|
* Staleness guard ported from the Vercept acquisition.
|
||||||
|
*
|
||||||
|
* Compares the model's last-seen screenshot against a fresh-right-now
|
||||||
|
* screenshot at the click target, so the model never clicks pixels it hasn't
|
||||||
|
* seen. If the 9×9 patch around the target differs, the click is aborted and
|
||||||
|
* the model is told to re-screenshot. This is NOT a popup detector.
|
||||||
|
*
|
||||||
|
* Semantics preserved exactly:
|
||||||
|
* - Skip on no `lastScreenshot` (cold start) — click proceeds.
|
||||||
|
* - Skip on any internal error (crop throws, screenshot fails, etc.) —
|
||||||
|
* click proceeds. Validation failure must never block the action.
|
||||||
|
* - 9×9 exact byte equality on raw pixel bytes. No fuzzing, no tolerance.
|
||||||
|
* - Compare in percentage coords so Retina scale doesn't matter.
|
||||||
|
*
|
||||||
|
* JPEG decode + crop is INJECTED via `ComputerUseHostAdapter.cropRawPatch`.
|
||||||
|
* The original used `sharp` (LGPL, native `.node` addon); we inject Electron's
|
||||||
|
* `nativeImage` (Chromium decoders, BSD, nothing to bundle) from the host, so
|
||||||
|
* this package never imports it — the crop is a function parameter.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ScreenshotResult } from "./executor.js";
|
||||||
|
import type { Logger } from "./types.js";
|
||||||
|
|
||||||
|
/** Injected by the host. See `ComputerUseHostAdapter.cropRawPatch`. */
|
||||||
|
export type CropRawPatchFn = (
|
||||||
|
jpegBase64: string,
|
||||||
|
rect: { x: number; y: number; width: number; height: number },
|
||||||
|
) => Buffer | null;
|
||||||
|
|
||||||
|
/** 9×9 is empirically the sweet spot — large enough to catch a tooltip
|
||||||
|
* appearing, small enough to not false-positive on surrounding animation.
|
||||||
|
**/
|
||||||
|
const DEFAULT_GRID_SIZE = 9;
|
||||||
|
|
||||||
|
export interface PixelCompareResult {
|
||||||
|
/** true → click may proceed. false → patch changed, abort the click. */
|
||||||
|
valid: boolean;
|
||||||
|
/** true → validation did not run (cold start, sub-gate off, or internal
|
||||||
|
* error). The caller MUST treat this identically to `valid: true`. */
|
||||||
|
skipped: boolean;
|
||||||
|
/** Populated when valid === false. Returned to the model verbatim. */
|
||||||
|
warning?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the crop rect for a patch centered on (xPercent, yPercent).
|
||||||
|
*
|
||||||
|
* Dimensions come from ScreenshotResult.width/height (physical pixels). Both
|
||||||
|
* screenshots have the same dimensions (same display, consecutive captures),
|
||||||
|
* so the rect is the same for both.
|
||||||
|
*/
|
||||||
|
function computeCropRect(
|
||||||
|
imgW: number,
|
||||||
|
imgH: number,
|
||||||
|
xPercent: number,
|
||||||
|
yPercent: number,
|
||||||
|
gridSize: number,
|
||||||
|
): { x: number; y: number; width: number; height: number } | null {
|
||||||
|
if (!imgW || !imgH) return null;
|
||||||
|
|
||||||
|
const clampedX = Math.max(0, Math.min(100, xPercent));
|
||||||
|
const clampedY = Math.max(0, Math.min(100, yPercent));
|
||||||
|
|
||||||
|
const centerX = Math.round((clampedX / 100.0) * imgW);
|
||||||
|
const centerY = Math.round((clampedY / 100.0) * imgH);
|
||||||
|
|
||||||
|
const halfGrid = Math.floor(gridSize / 2);
|
||||||
|
const cropX = Math.max(0, centerX - halfGrid);
|
||||||
|
const cropY = Math.max(0, centerY - halfGrid);
|
||||||
|
const cropW = Math.min(gridSize, imgW - cropX);
|
||||||
|
const cropH = Math.min(gridSize, imgH - cropY);
|
||||||
|
if (cropW <= 0 || cropH <= 0) return null;
|
||||||
|
|
||||||
|
return { x: cropX, y: cropY, width: cropW, height: cropH };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare the same patch location between two screenshots.
|
||||||
|
*
|
||||||
|
* @returns true when the raw pixel bytes are identical. false on any
|
||||||
|
* difference, or on any internal error (the caller treats an error here as
|
||||||
|
* `skipped`, so the false is harmless).
|
||||||
|
*/
|
||||||
|
export function comparePixelAtLocation(
|
||||||
|
crop: CropRawPatchFn,
|
||||||
|
lastScreenshot: ScreenshotResult,
|
||||||
|
freshScreenshot: ScreenshotResult,
|
||||||
|
xPercent: number,
|
||||||
|
yPercent: number,
|
||||||
|
gridSize: number = DEFAULT_GRID_SIZE,
|
||||||
|
): boolean {
|
||||||
|
// Both screenshots are of the same display — use the fresh one's
|
||||||
|
// dimensions (less likely to be stale than last's).
|
||||||
|
const rect = computeCropRect(
|
||||||
|
freshScreenshot.width,
|
||||||
|
freshScreenshot.height,
|
||||||
|
xPercent,
|
||||||
|
yPercent,
|
||||||
|
gridSize,
|
||||||
|
);
|
||||||
|
if (!rect) return false;
|
||||||
|
|
||||||
|
const patch1 = crop(lastScreenshot.base64, rect);
|
||||||
|
const patch2 = crop(freshScreenshot.base64, rect);
|
||||||
|
if (!patch1 || !patch2) return false;
|
||||||
|
|
||||||
|
// Direct buffer equality. Note: nativeImage.toBitmap() gives BGRA, sharp's
|
||||||
|
// .raw() gave RGB.
|
||||||
|
// Doesn't matter — we're comparing two same-format buffers for equality.
|
||||||
|
return patch1.equals(patch2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Battle-tested click-target validation ported from the Vercept acquisition,
|
||||||
|
* with the fresh-screenshot capture delegated to the caller (we don't have
|
||||||
|
* a global `SystemActions.takeScreenshot()` — the executor is injected).
|
||||||
|
*
|
||||||
|
* Skip conditions (any of these → `{ valid: true, skipped: true }`):
|
||||||
|
* - `lastScreenshot` is undefined (cold start).
|
||||||
|
* - `takeFreshScreenshot()` throws or returns null.
|
||||||
|
* - Injected crop function returns null (decode failure).
|
||||||
|
* - Any other exception.
|
||||||
|
*
|
||||||
|
* The caller decides whether to invoke this at all (sub-gate check lives
|
||||||
|
* in toolCalls.ts, not here).
|
||||||
|
*/
|
||||||
|
export async function validateClickTarget(
|
||||||
|
crop: CropRawPatchFn,
|
||||||
|
lastScreenshot: ScreenshotResult | undefined,
|
||||||
|
xPercent: number,
|
||||||
|
yPercent: number,
|
||||||
|
takeFreshScreenshot: () => Promise<ScreenshotResult | null>,
|
||||||
|
logger: Logger,
|
||||||
|
gridSize: number = DEFAULT_GRID_SIZE,
|
||||||
|
): Promise<PixelCompareResult> {
|
||||||
|
if (!lastScreenshot) {
|
||||||
|
return { valid: true, skipped: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fresh = await takeFreshScreenshot();
|
||||||
|
if (!fresh) {
|
||||||
|
return { valid: true, skipped: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
const pixelsMatch = comparePixelAtLocation(
|
||||||
|
crop,
|
||||||
|
lastScreenshot,
|
||||||
|
fresh,
|
||||||
|
xPercent,
|
||||||
|
yPercent,
|
||||||
|
gridSize,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pixelsMatch) {
|
||||||
|
return { valid: true, skipped: false };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
valid: false,
|
||||||
|
skipped: false,
|
||||||
|
warning:
|
||||||
|
"Screen content at the target location changed since the last screenshot. Take a new screenshot before clicking.",
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
// Skip validation on technical errors, execute action anyway.
|
||||||
|
// Battle-tested: validation failure must never block the click.
|
||||||
|
logger.debug("[pixelCompare] validation error, skipping", err);
|
||||||
|
return { valid: true, skipped: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/vendor/computer-use-mcp/sentinelApps.ts
vendored
Normal file
43
src/vendor/computer-use-mcp/sentinelApps.ts
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Bundle IDs that are escalations-in-disguise. The approval UI shows a warning
|
||||||
|
* badge for these; they are NOT blocked. Power users may legitimately want the
|
||||||
|
* model controlling a terminal.
|
||||||
|
*
|
||||||
|
* Imported by the renderer via the `./sentinelApps` subpath (package.json
|
||||||
|
* `exports`), which keeps Next.js from reaching index.ts → mcpServer.ts →
|
||||||
|
* @modelcontextprotocol/sdk (devDep, would fail module resolution). Keep
|
||||||
|
* this file import-free so the subpath stays clean.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** These apps can execute arbitrary shell commands. */
|
||||||
|
const SHELL_ACCESS_BUNDLE_IDS = new Set([
|
||||||
|
"com.apple.Terminal",
|
||||||
|
"com.googlecode.iterm2",
|
||||||
|
"com.microsoft.VSCode",
|
||||||
|
"dev.warp.Warp-Stable",
|
||||||
|
"com.github.wez.wezterm",
|
||||||
|
"io.alacritty",
|
||||||
|
"net.kovidgoyal.kitty",
|
||||||
|
"com.jetbrains.intellij",
|
||||||
|
"com.jetbrains.pycharm",
|
||||||
|
]);
|
||||||
|
|
||||||
|
/** Finder in the allowlist ≈ browse + open-any-file. */
|
||||||
|
const FILESYSTEM_ACCESS_BUNDLE_IDS = new Set(["com.apple.finder"]);
|
||||||
|
|
||||||
|
const SYSTEM_SETTINGS_BUNDLE_IDS = new Set(["com.apple.systempreferences"]);
|
||||||
|
|
||||||
|
export const SENTINEL_BUNDLE_IDS: ReadonlySet<string> = new Set([
|
||||||
|
...SHELL_ACCESS_BUNDLE_IDS,
|
||||||
|
...FILESYSTEM_ACCESS_BUNDLE_IDS,
|
||||||
|
...SYSTEM_SETTINGS_BUNDLE_IDS,
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type SentinelCategory = "shell" | "filesystem" | "system_settings";
|
||||||
|
|
||||||
|
export function getSentinelCategory(bundleId: string): SentinelCategory | null {
|
||||||
|
if (SHELL_ACCESS_BUNDLE_IDS.has(bundleId)) return "shell";
|
||||||
|
if (FILESYSTEM_ACCESS_BUNDLE_IDS.has(bundleId)) return "filesystem";
|
||||||
|
if (SYSTEM_SETTINGS_BUNDLE_IDS.has(bundleId)) return "system_settings";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
19
src/vendor/computer-use-mcp/subGates.ts
vendored
Normal file
19
src/vendor/computer-use-mcp/subGates.ts
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import type { CuSubGates } from './types.js'
|
||||||
|
|
||||||
|
export const ALL_SUB_GATES_ON: CuSubGates = {
|
||||||
|
pixelValidation: false,
|
||||||
|
clipboardPasteMultiline: true,
|
||||||
|
mouseAnimation: true,
|
||||||
|
hideBeforeAction: true,
|
||||||
|
autoTargetDisplay: true,
|
||||||
|
clipboardGuard: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ALL_SUB_GATES_OFF: CuSubGates = {
|
||||||
|
pixelValidation: false,
|
||||||
|
clipboardPasteMultiline: false,
|
||||||
|
mouseAnimation: false,
|
||||||
|
hideBeforeAction: false,
|
||||||
|
autoTargetDisplay: false,
|
||||||
|
clipboardGuard: false,
|
||||||
|
}
|
||||||
3652
src/vendor/computer-use-mcp/toolCalls.ts
vendored
Normal file
3652
src/vendor/computer-use-mcp/toolCalls.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
706
src/vendor/computer-use-mcp/tools.ts
vendored
Normal file
706
src/vendor/computer-use-mcp/tools.ts
vendored
Normal file
@ -0,0 +1,706 @@
|
|||||||
|
/**
|
||||||
|
* MCP tool schemas for the computer-use server. Mirrors
|
||||||
|
* claude-for-chrome-mcp/src/browserTools.ts in shape (plain `Tool`-shaped
|
||||||
|
* object literals, no zod).
|
||||||
|
*
|
||||||
|
* Coordinate descriptions are baked in at tool-list build time from the
|
||||||
|
* `chicago_coordinate_mode` gate. The model sees exactly ONE coordinate
|
||||||
|
* convention in the param descriptions and never learns the other exists.
|
||||||
|
* The host (`serverDef.ts`) reads the same frozen gate value for
|
||||||
|
* `scaleCoord` — both must agree or clicks land in the wrong space.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
|
||||||
|
import type { CoordinateMode } from "./types.js";
|
||||||
|
|
||||||
|
// See packages/desktop/computer-use-mcp/COORDINATES.md before touching any
|
||||||
|
// model-facing coordinate text. Chrome's browserTools.ts:143 is the reference
|
||||||
|
// phrasing — "pixels from the left edge", no geometry, no number to do math with.
|
||||||
|
const COORD_DESC: Record<CoordinateMode, { x: string; y: string }> = {
|
||||||
|
pixels: {
|
||||||
|
x: "Horizontal pixel position read directly from the most recent screenshot image, measured from the left edge. The server handles all scaling.",
|
||||||
|
y: "Vertical pixel position read directly from the most recent screenshot image, measured from the top edge. The server handles all scaling.",
|
||||||
|
},
|
||||||
|
normalized_0_100: {
|
||||||
|
x: "Horizontal position as a percentage of screen width, 0.0–100.0 (0 = left edge, 100 = right edge).",
|
||||||
|
y: "Vertical position as a percentage of screen height, 0.0–100.0 (0 = top edge, 100 = bottom edge).",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const FRONTMOST_GATE_DESC =
|
||||||
|
"The frontmost application must be in the session allowlist at the time of this call, or this tool returns an error and does nothing.";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Item schema for the `actions` array in `computer_batch`, `teach_step`, and
|
||||||
|
* `teach_batch`. All three dispatch through the same `dispatchAction` path
|
||||||
|
* with the same validation — keep this enum in sync with `BATCHABLE_ACTIONS`
|
||||||
|
* in toolCalls.ts.
|
||||||
|
*/
|
||||||
|
const BATCH_ACTION_ITEM_SCHEMA = {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
action: {
|
||||||
|
type: "string",
|
||||||
|
enum: [
|
||||||
|
"key",
|
||||||
|
"type",
|
||||||
|
"mouse_move",
|
||||||
|
"left_click",
|
||||||
|
"left_click_drag",
|
||||||
|
"right_click",
|
||||||
|
"middle_click",
|
||||||
|
"double_click",
|
||||||
|
"triple_click",
|
||||||
|
"scroll",
|
||||||
|
"hold_key",
|
||||||
|
"screenshot",
|
||||||
|
"cursor_position",
|
||||||
|
"left_mouse_down",
|
||||||
|
"left_mouse_up",
|
||||||
|
"wait",
|
||||||
|
],
|
||||||
|
description: "The action to perform.",
|
||||||
|
},
|
||||||
|
coordinate: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "number" },
|
||||||
|
minItems: 2,
|
||||||
|
maxItems: 2,
|
||||||
|
description:
|
||||||
|
"(x, y) for click/mouse_move/scroll/left_click_drag end point.",
|
||||||
|
},
|
||||||
|
start_coordinate: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "number" },
|
||||||
|
minItems: 2,
|
||||||
|
maxItems: 2,
|
||||||
|
description:
|
||||||
|
"(x, y) drag start — left_click_drag only. Omit to drag from current cursor.",
|
||||||
|
},
|
||||||
|
text: {
|
||||||
|
type: "string",
|
||||||
|
description:
|
||||||
|
"For type: the text. For key/hold_key: the chord string. For click/scroll: modifier keys to hold.",
|
||||||
|
},
|
||||||
|
scroll_direction: {
|
||||||
|
type: "string",
|
||||||
|
enum: ["up", "down", "left", "right"],
|
||||||
|
},
|
||||||
|
scroll_amount: { type: "integer", minimum: 0, maximum: 100 },
|
||||||
|
duration: {
|
||||||
|
type: "number",
|
||||||
|
description: "Seconds (0–100). For hold_key/wait.",
|
||||||
|
},
|
||||||
|
repeat: {
|
||||||
|
type: "integer",
|
||||||
|
minimum: 1,
|
||||||
|
maximum: 100,
|
||||||
|
description: "For key: repeat count.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["action"],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the tool list. Parameterized by capabilities and coordinate mode so
|
||||||
|
* descriptions are honest and unambiguous (plan §1 — "Unfiltered + honest").
|
||||||
|
*
|
||||||
|
* `coordinateMode` MUST match what the host passes to `scaleCoord` at tool-
|
||||||
|
* -call time. Both should read the same frozen-at-load gate constant.
|
||||||
|
*
|
||||||
|
* `installedAppNames` — optional pre-sanitized list of app display names to
|
||||||
|
* enumerate in the `request_access` description. The caller is responsible
|
||||||
|
* for sanitization (length cap, character allowlist, sort, count cap) —
|
||||||
|
* this function just splices the list into the description verbatim. Omit
|
||||||
|
* to fall back to the generic "display names or bundle IDs" wording.
|
||||||
|
*/
|
||||||
|
export function buildComputerUseTools(
|
||||||
|
caps: {
|
||||||
|
screenshotFiltering: "native" | "none";
|
||||||
|
platform: "darwin" | "win32";
|
||||||
|
/** Include request_teach_access + teach_step. Read once at server construction. */
|
||||||
|
teachMode?: boolean;
|
||||||
|
},
|
||||||
|
coordinateMode: CoordinateMode,
|
||||||
|
installedAppNames?: string[],
|
||||||
|
): Tool[] {
|
||||||
|
const coord = COORD_DESC[coordinateMode];
|
||||||
|
|
||||||
|
// Shared hint suffix for BOTH request_access and request_teach_access —
|
||||||
|
// they use the same resolveRequestedApps path, so the model should get
|
||||||
|
// the same enumeration for both.
|
||||||
|
const installedAppsHint =
|
||||||
|
installedAppNames && installedAppNames.length > 0
|
||||||
|
? ` Available applications on this machine: ${installedAppNames.join(", ")}.`
|
||||||
|
: "";
|
||||||
|
|
||||||
|
// [x, y]` tuple — param shape for all
|
||||||
|
// click/move/scroll tools.
|
||||||
|
const coordinateTuple = {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "number" },
|
||||||
|
minItems: 2,
|
||||||
|
maxItems: 2,
|
||||||
|
description: `(x, y): ${coord.x}`,
|
||||||
|
};
|
||||||
|
// Modifier hold during click. Shared across all 5 click variants.
|
||||||
|
const clickModifierText = {
|
||||||
|
type: "string",
|
||||||
|
description:
|
||||||
|
'Modifier keys to hold during the click (e.g. "shift", "ctrl+shift"). Supports the same syntax as the key tool.',
|
||||||
|
};
|
||||||
|
|
||||||
|
const screenshotDesc =
|
||||||
|
caps.screenshotFiltering === "native"
|
||||||
|
? "Take a screenshot of the primary display. Applications not in the session allowlist are excluded at the compositor level — only granted apps and the desktop are visible."
|
||||||
|
: "Take a screenshot of the primary display. On this platform, screenshots are NOT filtered — all open windows are visible. Input actions targeting apps not in the session allowlist are rejected.";
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: "request_access",
|
||||||
|
description:
|
||||||
|
"Request user permission to control a set of applications for this session. Must be called before any other tool in this server. " +
|
||||||
|
"The user sees a single dialog listing all requested apps and either allows the whole set or denies it. " +
|
||||||
|
"Call this again mid-session to add more apps; previously granted apps remain granted. " +
|
||||||
|
"Returns the granted apps, denied apps, and screenshot filtering capability.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
apps: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "string" },
|
||||||
|
description:
|
||||||
|
"Application display names (e.g. \"Slack\", \"Calendar\") or bundle identifiers (e.g. \"com.tinyspeck.slackmacgap\"). Display names are resolved case-insensitively against installed apps." +
|
||||||
|
installedAppsHint,
|
||||||
|
},
|
||||||
|
reason: {
|
||||||
|
type: "string",
|
||||||
|
description:
|
||||||
|
"One-sentence explanation shown to the user in the approval dialog. Explain the task, not the mechanism.",
|
||||||
|
},
|
||||||
|
clipboardRead: {
|
||||||
|
type: "boolean",
|
||||||
|
description:
|
||||||
|
"Also request permission to read the user's clipboard (separate checkbox in the dialog).",
|
||||||
|
},
|
||||||
|
clipboardWrite: {
|
||||||
|
type: "boolean",
|
||||||
|
description:
|
||||||
|
"Also request permission to write the user's clipboard. When granted, multi-line `type` calls use the clipboard fast path.",
|
||||||
|
},
|
||||||
|
systemKeyCombos: {
|
||||||
|
type: "boolean",
|
||||||
|
description:
|
||||||
|
"Also request permission to send system-level key combos (quit app, switch app, lock screen). Without this, those specific combos are blocked.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["apps", "reason"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "screenshot",
|
||||||
|
description:
|
||||||
|
screenshotDesc +
|
||||||
|
" Returns an error if the allowlist is empty. The returned image is what subsequent click coordinates are relative to.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
save_to_disk: {
|
||||||
|
type: "boolean",
|
||||||
|
description:
|
||||||
|
"Save the image to disk so it can be attached to a message for the user. Returns the saved path in the tool result. Only set this when you intend to share the image — screenshots you're just looking at don't need saving.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "zoom",
|
||||||
|
description:
|
||||||
|
"Take a higher-resolution screenshot of a specific region of the last full-screen screenshot. Use this liberally to inspect small text, button labels, or fine UI details that are hard to read in the downsampled full-screen image. " +
|
||||||
|
"IMPORTANT: Coordinates in subsequent click calls always refer to the full-screen screenshot, never the zoomed image. This tool is read-only for inspecting detail.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
region: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "integer" },
|
||||||
|
minItems: 4,
|
||||||
|
maxItems: 4,
|
||||||
|
description:
|
||||||
|
"(x0, y0, x1, y1): Rectangle to zoom into, in the coordinate space of the most recent full-screen screenshot. x0,y0 = top-left, x1,y1 = bottom-right.",
|
||||||
|
},
|
||||||
|
save_to_disk: {
|
||||||
|
type: "boolean",
|
||||||
|
description:
|
||||||
|
"Save the image to disk so it can be attached to a message for the user. Returns the saved path in the tool result. Only set this when you intend to share the image.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["region"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "left_click",
|
||||||
|
description: `Left-click at the given coordinates. ${FRONTMOST_GATE_DESC}`,
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
coordinate: coordinateTuple,
|
||||||
|
text: clickModifierText,
|
||||||
|
},
|
||||||
|
required: ["coordinate"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "double_click",
|
||||||
|
description: `Double-click at the given coordinates. Selects a word in most text editors. ${FRONTMOST_GATE_DESC}`,
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
coordinate: coordinateTuple,
|
||||||
|
text: clickModifierText,
|
||||||
|
},
|
||||||
|
required: ["coordinate"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "triple_click",
|
||||||
|
description: `Triple-click at the given coordinates. Selects a line in most text editors. ${FRONTMOST_GATE_DESC}`,
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
coordinate: coordinateTuple,
|
||||||
|
text: clickModifierText,
|
||||||
|
},
|
||||||
|
required: ["coordinate"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "right_click",
|
||||||
|
description: `Right-click at the given coordinates. Opens a context menu in most applications. ${FRONTMOST_GATE_DESC}`,
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
coordinate: coordinateTuple,
|
||||||
|
text: clickModifierText,
|
||||||
|
},
|
||||||
|
required: ["coordinate"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "middle_click",
|
||||||
|
description: `Middle-click (scroll-wheel click) at the given coordinates. ${FRONTMOST_GATE_DESC}`,
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
coordinate: coordinateTuple,
|
||||||
|
text: clickModifierText,
|
||||||
|
},
|
||||||
|
required: ["coordinate"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "type",
|
||||||
|
description: `Type text into whatever currently has keyboard focus. ${FRONTMOST_GATE_DESC} Newlines are supported. For keyboard shortcuts use \`key\` instead.`,
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
text: { type: "string", description: "Text to type." },
|
||||||
|
},
|
||||||
|
required: ["text"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "key",
|
||||||
|
description:
|
||||||
|
`Press a key or key combination (e.g. "return", "escape", "cmd+a", "ctrl+shift+tab"). ${FRONTMOST_GATE_DESC} ` +
|
||||||
|
"System-level combos (quit app, switch app, lock screen) require the `systemKeyCombos` grant — without it they return an error. All other combos work.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
text: {
|
||||||
|
type: "string",
|
||||||
|
description: 'Modifiers joined with "+", e.g. "cmd+shift+a".',
|
||||||
|
},
|
||||||
|
repeat: {
|
||||||
|
type: "integer",
|
||||||
|
minimum: 1,
|
||||||
|
maximum: 100,
|
||||||
|
description: "Number of times to repeat the key press. Default is 1.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["text"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "scroll",
|
||||||
|
description: `Scroll at the given coordinates. ${FRONTMOST_GATE_DESC}`,
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
coordinate: coordinateTuple,
|
||||||
|
scroll_direction: {
|
||||||
|
type: "string",
|
||||||
|
enum: ["up", "down", "left", "right"],
|
||||||
|
description: "Direction to scroll.",
|
||||||
|
},
|
||||||
|
scroll_amount: {
|
||||||
|
type: "integer",
|
||||||
|
minimum: 0,
|
||||||
|
maximum: 100,
|
||||||
|
description: "Number of scroll ticks.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["coordinate", "scroll_direction", "scroll_amount"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "left_click_drag",
|
||||||
|
description: `Press, move to target, and release. ${FRONTMOST_GATE_DESC}`,
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
coordinate: {
|
||||||
|
...coordinateTuple,
|
||||||
|
description: `(x, y) end point: ${coord.x}`,
|
||||||
|
},
|
||||||
|
start_coordinate: {
|
||||||
|
...coordinateTuple,
|
||||||
|
description: `(x, y) start point. If omitted, drags from the current cursor position. ${coord.x}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["coordinate"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "mouse_move",
|
||||||
|
description: `Move the mouse cursor without clicking. Useful for triggering hover states. ${FRONTMOST_GATE_DESC}`,
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
coordinate: coordinateTuple,
|
||||||
|
},
|
||||||
|
required: ["coordinate"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "open_application",
|
||||||
|
description:
|
||||||
|
"Bring an application to the front, launching it if necessary. The target application must already be in the session allowlist — call request_access first.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
app: {
|
||||||
|
type: "string",
|
||||||
|
description:
|
||||||
|
"Display name (e.g. \"Slack\") or bundle identifier (e.g. \"com.tinyspeck.slackmacgap\").",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["app"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "switch_display",
|
||||||
|
description:
|
||||||
|
"Switch which monitor subsequent screenshots capture. Use this when the " +
|
||||||
|
"application you need is on a different monitor than the one shown. " +
|
||||||
|
"The screenshot tool tells you which monitor it captured and lists " +
|
||||||
|
"other attached monitors by name — pass one of those names here. " +
|
||||||
|
"After switching, call screenshot to see the new monitor. " +
|
||||||
|
'Pass "auto" to return to automatic monitor selection.',
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
display: {
|
||||||
|
type: "string",
|
||||||
|
description:
|
||||||
|
'Monitor name from the screenshot note (e.g. "Built-in Retina Display", ' +
|
||||||
|
'"LG UltraFine"), or "auto" to re-enable automatic selection.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["display"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "list_granted_applications",
|
||||||
|
description:
|
||||||
|
"List the applications currently in the session allowlist, plus the active grant flags and coordinate mode. No side effects.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {},
|
||||||
|
required: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "read_clipboard",
|
||||||
|
description:
|
||||||
|
"Read the current clipboard contents as text. Requires the `clipboardRead` grant.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {},
|
||||||
|
required: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "write_clipboard",
|
||||||
|
description:
|
||||||
|
"Write text to the clipboard. Requires the `clipboardWrite` grant.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
text: { type: "string" },
|
||||||
|
},
|
||||||
|
required: ["text"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "wait",
|
||||||
|
description: "Wait for a specified duration.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
duration: {
|
||||||
|
type: "number",
|
||||||
|
description: "Duration in seconds (0–100).",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["duration"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "cursor_position",
|
||||||
|
description:
|
||||||
|
"Get the current mouse cursor position. Returns image-pixel coordinates relative to the most recent screenshot, or logical points if no screenshot has been taken.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {},
|
||||||
|
required: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "hold_key",
|
||||||
|
description:
|
||||||
|
`Press and hold a key or key combination for the specified duration, then release. ${FRONTMOST_GATE_DESC} ` +
|
||||||
|
"System-level combos require the `systemKeyCombos` grant.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
text: {
|
||||||
|
type: "string",
|
||||||
|
description: 'Key or chord to hold, e.g. "space", "shift+down".',
|
||||||
|
},
|
||||||
|
duration: {
|
||||||
|
type: "number",
|
||||||
|
description: "Duration in seconds (0–100).",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["text", "duration"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "left_mouse_down",
|
||||||
|
description:
|
||||||
|
`Press the left mouse button at the current cursor position and leave it held. ${FRONTMOST_GATE_DESC} ` +
|
||||||
|
"Use mouse_move first to position the cursor. Call left_mouse_up to release. Errors if the button is already held.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {},
|
||||||
|
required: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "left_mouse_up",
|
||||||
|
description:
|
||||||
|
`Release the left mouse button at the current cursor position. ${FRONTMOST_GATE_DESC} ` +
|
||||||
|
"Pairs with left_mouse_down. Safe to call even if the button is not currently held.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {},
|
||||||
|
required: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "computer_batch",
|
||||||
|
description:
|
||||||
|
"Execute a sequence of actions in ONE tool call. Each individual tool call requires a model→API round trip (seconds); " +
|
||||||
|
"batching a predictable sequence eliminates all but one. Use this whenever you can predict the outcome of several actions ahead — " +
|
||||||
|
"e.g. click a field, type into it, press Return. Actions execute sequentially and stop on the first error. " +
|
||||||
|
`${FRONTMOST_GATE_DESC} The frontmost check runs before EACH action inside the batch — if an action opens a non-allowed app, the next action's gate fires and the batch stops there. ` +
|
||||||
|
"Mid-batch screenshot actions are allowed for inspection but coordinates in subsequent clicks always refer to the PRE-BATCH full-screen screenshot.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
type: "array",
|
||||||
|
minItems: 1,
|
||||||
|
items: BATCH_ACTION_ITEM_SCHEMA,
|
||||||
|
description:
|
||||||
|
'List of actions. Example: [{"action":"left_click","coordinate":[100,200]},{"action":"type","text":"hello"},{"action":"key","text":"Return"}]',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["actions"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
...(caps.teachMode ? buildTeachTools(coord, installedAppsHint) : []),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Teach-mode tools. Split out so the spread above stays a single expression;
|
||||||
|
* takes `coord` so `teach_step.anchor`'s description uses the same
|
||||||
|
* frozen coordinate-mode phrasing as click coords, and `installedAppsHint`
|
||||||
|
* so `request_teach_access.apps` gets the same enumeration as
|
||||||
|
* `request_access.apps` (same resolution path → same hint).
|
||||||
|
*/
|
||||||
|
function buildTeachTools(
|
||||||
|
coord: { x: string; y: string },
|
||||||
|
installedAppsHint: string,
|
||||||
|
): Tool[] {
|
||||||
|
// Shared between teach_step (top-level) and teach_batch (inside steps[]
|
||||||
|
// items). Depends on coord, so it lives inside this factory.
|
||||||
|
const teachStepProperties = {
|
||||||
|
explanation: {
|
||||||
|
type: "string",
|
||||||
|
description:
|
||||||
|
"Tooltip body text. Explain what the user is looking at and why it matters. " +
|
||||||
|
"This is the ONLY place the user sees your words — be complete but concise.",
|
||||||
|
},
|
||||||
|
next_preview: {
|
||||||
|
type: "string",
|
||||||
|
description:
|
||||||
|
"One line describing exactly what will happen when the user clicks Next. " +
|
||||||
|
'Example: "Next: I\'ll click Create Bucket and type the name." ' +
|
||||||
|
"Shown below the explanation in a smaller font.",
|
||||||
|
},
|
||||||
|
anchor: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "number" },
|
||||||
|
minItems: 2,
|
||||||
|
maxItems: 2,
|
||||||
|
description:
|
||||||
|
`(x, y) — where the tooltip arrow points. ${coord.x} ` +
|
||||||
|
"Omit to center the tooltip with no arrow (for general-context steps).",
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
type: "array",
|
||||||
|
// Empty allowed — "read this, click Next" steps.
|
||||||
|
items: BATCH_ACTION_ITEM_SCHEMA,
|
||||||
|
description:
|
||||||
|
"Actions to execute when the user clicks Next. Same item schema as computer_batch.actions. " +
|
||||||
|
"Empty array is valid for purely explanatory steps. Actions run sequentially and stop on first error.",
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: "request_teach_access",
|
||||||
|
description:
|
||||||
|
"Request permission to guide the user through a task step-by-step with on-screen tooltips. " +
|
||||||
|
"Use this INSTEAD OF request_access when the user wants to LEARN how to do something " +
|
||||||
|
'(phrases like "teach me", "walk me through", "show me how", "help me learn"). ' +
|
||||||
|
"On approval the main Claude window hides and a fullscreen tooltip overlay appears. " +
|
||||||
|
"You then call teach_step repeatedly; each call shows one tooltip and waits for the user to click Next. " +
|
||||||
|
"Same app-allowlist semantics as request_access, but no clipboard/system-key flags. " +
|
||||||
|
"Teach mode ends automatically when your turn ends.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
apps: {
|
||||||
|
type: "array",
|
||||||
|
items: { type: "string" },
|
||||||
|
description:
|
||||||
|
'Application display names (e.g. "Slack", "Calendar") or bundle identifiers. Resolved case-insensitively against installed apps.' +
|
||||||
|
installedAppsHint,
|
||||||
|
},
|
||||||
|
reason: {
|
||||||
|
type: "string",
|
||||||
|
description:
|
||||||
|
'What you will be teaching. Shown in the approval dialog as "Claude wants to guide you through {reason}". Keep it short and task-focused.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["apps", "reason"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "teach_step",
|
||||||
|
description:
|
||||||
|
"Show one guided-tour tooltip and wait for the user to click Next. On Next, execute the actions, " +
|
||||||
|
"take a fresh screenshot, and return both — you do NOT need a separate screenshot call between steps. " +
|
||||||
|
"The returned image shows the state after your actions ran; anchor the next teach_step against it. " +
|
||||||
|
"IMPORTANT — the user only sees the tooltip during teach mode. Put ALL narration in `explanation`. " +
|
||||||
|
"Text you emit outside teach_step calls is NOT visible until teach mode ends. " +
|
||||||
|
"Pack as many actions as possible into each step's `actions` array — the user waits through " +
|
||||||
|
"the whole round trip between clicks, so one step that fills a form beats five steps that fill one field each. " +
|
||||||
|
"Returns {exited:true} if the user clicks Exit — do not call teach_step again after that. " +
|
||||||
|
"Take an initial screenshot before your FIRST teach_step to anchor it.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: teachStepProperties,
|
||||||
|
required: ["explanation", "next_preview", "actions"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "teach_batch",
|
||||||
|
description:
|
||||||
|
"Queue multiple teach steps in one tool call. Parallels computer_batch: " +
|
||||||
|
"N steps → one model↔API round trip instead of N. Each step still shows a tooltip " +
|
||||||
|
"and waits for the user's Next click, but YOU aren't waiting for a round trip between steps. " +
|
||||||
|
"You can call teach_batch multiple times in one tour — treat each batch as one predictable " +
|
||||||
|
"SEGMENT (typically: all the steps on one page). The returned screenshot shows the state " +
|
||||||
|
"after the batch's final actions; anchor the NEXT teach_batch against it. " +
|
||||||
|
"WITHIN a batch, all anchors and click coordinates refer to the PRE-BATCH screenshot " +
|
||||||
|
"(same invariant as computer_batch) — for steps 2+ in a batch, either omit anchor " +
|
||||||
|
"(centered tooltip) or target elements you know won't have moved. " +
|
||||||
|
"Good pattern: batch 5 tooltips on page A (last step navigates) → read returned screenshot → " +
|
||||||
|
"batch 3 tooltips on page B → done. " +
|
||||||
|
"Returns {exited:true, stepsCompleted:N} if the user clicks Exit — do NOT call again after that; " +
|
||||||
|
"{stepsCompleted, stepFailed, ...} if an action errors mid-batch; " +
|
||||||
|
"otherwise {stepsCompleted, results:[...]} plus a final screenshot. " +
|
||||||
|
"Fall back to individual teach_step calls when you need to react to each intermediate screenshot.",
|
||||||
|
inputSchema: {
|
||||||
|
type: "object" as const,
|
||||||
|
properties: {
|
||||||
|
steps: {
|
||||||
|
type: "array",
|
||||||
|
minItems: 1,
|
||||||
|
items: {
|
||||||
|
type: "object",
|
||||||
|
properties: teachStepProperties,
|
||||||
|
required: ["explanation", "next_preview", "actions"],
|
||||||
|
},
|
||||||
|
description:
|
||||||
|
"Ordered steps. Validated upfront — a typo in step 5 errors before any tooltip shows.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["steps"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
621
src/vendor/computer-use-mcp/types.ts
vendored
Normal file
621
src/vendor/computer-use-mcp/types.ts
vendored
Normal file
@ -0,0 +1,621 @@
|
|||||||
|
import type {
|
||||||
|
ComputerExecutor,
|
||||||
|
InstalledApp,
|
||||||
|
ScreenshotResult,
|
||||||
|
} from "./executor.js";
|
||||||
|
|
||||||
|
/** `ScreenshotResult` without the base64 blob. The shape hosts persist for
|
||||||
|
* cross-respawn `scaleCoord` survival. */
|
||||||
|
export type ScreenshotDims = Omit<ScreenshotResult, "base64">;
|
||||||
|
|
||||||
|
/** Shape mirrors claude-for-chrome-mcp/src/types.ts:1-7 */
|
||||||
|
export interface Logger {
|
||||||
|
info: (message: string, ...args: unknown[]) => void;
|
||||||
|
error: (message: string, ...args: unknown[]) => void;
|
||||||
|
warn: (message: string, ...args: unknown[]) => void;
|
||||||
|
debug: (message: string, ...args: unknown[]) => void;
|
||||||
|
silly: (message: string, ...args: unknown[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Per-app permission tier. Hardcoded by category at grant time — the
|
||||||
|
* approval dialog displays the tier but the user cannot change it (for now).
|
||||||
|
*
|
||||||
|
* - `"read"` — visible in screenshots, NO interaction (no clicks, no typing).
|
||||||
|
* Browsers land here: the model can read a page that's already open, but
|
||||||
|
* must use the Claude-in-Chrome MCP for any navigation/clicking. Trading
|
||||||
|
* platforms land here too (no CiC alternative — the model asks the user).
|
||||||
|
* - `"click"` — visible + plain left-click, scroll. NO typing/keys,
|
||||||
|
* NO right/middle-click, NO modifier-clicks, NO drag-drop (all text-
|
||||||
|
* injection vectors). Terminals/IDEs land here: the model can click a
|
||||||
|
* Run button or scroll test output, but `type("rm -rf /")` is blocked
|
||||||
|
* and so is right-click→Paste and dragging text onto the terminal.
|
||||||
|
* - `"full"` — visible + click + type/key/paste. Everything else.
|
||||||
|
*
|
||||||
|
* Enforced in `runInputActionGates` via the frontmost-app check: keyboard
|
||||||
|
* actions require `"full"`, mouse actions require `"click"` or higher.
|
||||||
|
*/
|
||||||
|
export type CuAppPermTier = "read" | "click" | "full";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single app the user has approved for the current session. Session-scoped
|
||||||
|
* only — there is no "once" or "forever" scope (unlike Chrome's per-domain
|
||||||
|
* three-way). CU has no natural "once" unit; one task = hundreds of clicks.
|
||||||
|
* Mirrors how `chromeAllowedDomains` is a plain `string[]` with no per-item
|
||||||
|
* scope.
|
||||||
|
*/
|
||||||
|
export interface AppGrant {
|
||||||
|
bundleId: string;
|
||||||
|
displayName: string;
|
||||||
|
/** Epoch ms. For Settings-page display ("Granted 3m ago"). */
|
||||||
|
grantedAt: number;
|
||||||
|
/** Undefined → `"full"` (back-compat for pre-tier grants persisted in
|
||||||
|
* session state). */
|
||||||
|
tier?: CuAppPermTier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Orthogonal to the app allowlist. */
|
||||||
|
export interface CuGrantFlags {
|
||||||
|
clipboardRead: boolean;
|
||||||
|
clipboardWrite: boolean;
|
||||||
|
/**
|
||||||
|
* When false, the `key` tool rejects combos in `keyBlocklist.ts`
|
||||||
|
* (cmd+q, cmd+tab, cmd+space, cmd+shift+q, ctrl+alt+delete). All other
|
||||||
|
* key sequences work regardless.
|
||||||
|
*/
|
||||||
|
systemKeyCombos: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DEFAULT_GRANT_FLAGS: CuGrantFlags = {
|
||||||
|
clipboardRead: false,
|
||||||
|
clipboardWrite: false,
|
||||||
|
systemKeyCombos: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Host picks via GrowthBook JSON feature `chicago_coordinate_mode`, baked
|
||||||
|
* into tool param descriptions at server-construction time. The model sees
|
||||||
|
* ONE convention and never learns the other exists. `normalized_0_100`
|
||||||
|
* sidesteps the Retina scaleFactor bug class entirely.
|
||||||
|
*/
|
||||||
|
export type CoordinateMode = "pixels" | "normalized_0_100";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Independent kill switches for subtle/risky ported behaviors. Read from
|
||||||
|
* GrowthBook by the host adapter, consulted in `toolCalls.ts`.
|
||||||
|
*/
|
||||||
|
export interface CuSubGates {
|
||||||
|
/** 9×9 exact-byte staleness guard before click. */
|
||||||
|
pixelValidation: boolean;
|
||||||
|
/** Route `type("foo\nbar")` through clipboard instead of keystroke-by-keystroke. */
|
||||||
|
clipboardPasteMultiline: boolean;
|
||||||
|
/**
|
||||||
|
* Ease-out-cubic mouse glide at 60fps, distance-proportional duration
|
||||||
|
* (2000 px/sec, capped at 0.5s). Adds up to ~0.5s latency
|
||||||
|
* per click. When off, cursor teleports instantly.
|
||||||
|
*/
|
||||||
|
mouseAnimation: boolean;
|
||||||
|
/**
|
||||||
|
* Pre-action sequence: hide non-allowlisted apps, then defocus us (from the
|
||||||
|
* Vercept acquisition). When off, the
|
||||||
|
* frontmost gate fires in the normal case and the model gets stuck — this
|
||||||
|
* is the A/B-test-the-old-broken-behavior switch.
|
||||||
|
*/
|
||||||
|
hideBeforeAction: boolean;
|
||||||
|
/**
|
||||||
|
* Auto-resolve the target display before each screenshot when the
|
||||||
|
* selected display has no allowed-app windows. When on, `handleScreenshot`
|
||||||
|
* uses the atomic Swift path; off → sticks with `selectedDisplayId`.
|
||||||
|
*/
|
||||||
|
autoTargetDisplay: boolean;
|
||||||
|
/**
|
||||||
|
* Stash+clear the clipboard while a tier-"click" app is frontmost.
|
||||||
|
* Closes the gap where a click-tier terminal/IDE has a UI Paste button
|
||||||
|
* that's plain-left-clickable — without this, the tier "click"
|
||||||
|
* keyboard block can be routed around by clicking Paste. Restored when
|
||||||
|
* a non-"click" app becomes frontmost, or at turn end.
|
||||||
|
*/
|
||||||
|
clipboardGuard: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Permission request/response (mirror of BridgePermissionRequest, types.ts:77-94)
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** One entry per app the model asked for, after name → bundle ID resolution. */
|
||||||
|
export interface ResolvedAppRequest {
|
||||||
|
/** What the model asked for (e.g. "Slack", "com.tinyspeck.slackmacgap"). */
|
||||||
|
requestedName: string;
|
||||||
|
/** The resolved InstalledApp if found, else undefined (shown greyed in the UI). */
|
||||||
|
resolved?: InstalledApp;
|
||||||
|
/** Shell-access-equivalent bundle IDs get a UI warning. See sentinelApps.ts. */
|
||||||
|
isSentinel: boolean;
|
||||||
|
/** Already in the allowlist → skip the checkbox, return in `granted` immediately. */
|
||||||
|
alreadyGranted: boolean;
|
||||||
|
/** Hardcoded tier for this app (browser→"read", terminal→"click", else "full").
|
||||||
|
* The dialog displays this read-only; the renderer passes it through
|
||||||
|
* verbatim in the AppGrant. */
|
||||||
|
proposedTier: CuAppPermTier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payload for the renderer approval dialog. Rides through the existing
|
||||||
|
* `ToolPermissionRequest.input: unknown` field
|
||||||
|
* (packages/utils/desktop/bridge/common/claude.web.ts:1262) — no IPC schema
|
||||||
|
* change needed.
|
||||||
|
*/
|
||||||
|
export interface CuPermissionRequest {
|
||||||
|
requestId: string;
|
||||||
|
/** Model-provided reason string. Shown prominently in the approval UI. */
|
||||||
|
reason: string;
|
||||||
|
apps: ResolvedAppRequest[];
|
||||||
|
/** What the model asked for. User can toggle independently of apps. */
|
||||||
|
requestedFlags: Partial<CuGrantFlags>;
|
||||||
|
/**
|
||||||
|
* For the "On Windows, Claude can see all apps..." footnote. Taken from
|
||||||
|
* `executor.capabilities.screenshotFiltering` so the renderer doesn't
|
||||||
|
* need to know about platforms.
|
||||||
|
*/
|
||||||
|
screenshotFiltering: "native" | "none";
|
||||||
|
/**
|
||||||
|
* Present only when TCC permissions are NOT yet granted. When present,
|
||||||
|
* the renderer shows a TCC toggle panel (two rows: Accessibility, Screen
|
||||||
|
* Recording) INSTEAD OF the app list. Clicking a row's "Request" button
|
||||||
|
* triggers the OS prompt; the store polls on window-focus and flips the
|
||||||
|
* toggle when the grant is detected. macOS itself prompts the user to
|
||||||
|
* restart after granting Screen Recording — we don't.
|
||||||
|
*/
|
||||||
|
tccState?: {
|
||||||
|
accessibility: boolean;
|
||||||
|
screenRecording: boolean;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Apps with windows on the CU display that aren't in the requested
|
||||||
|
* allowlist. These will be hidden the first time Claude takes an action.
|
||||||
|
* Computed at request_access time — may be slightly stale by the time the
|
||||||
|
* user clicks Allow, but it's a preview, not a contract. Absent when
|
||||||
|
* empty so the renderer can skip the section cleanly.
|
||||||
|
*/
|
||||||
|
willHide?: Array<{ bundleId: string; displayName: string }>;
|
||||||
|
/**
|
||||||
|
* `chicagoAutoUnhide` app preference at request time. The renderer picks
|
||||||
|
* between "...then restored when Claude is done" and "...will be hidden"
|
||||||
|
* copy. Absent when `willHide` is absent (same condition).
|
||||||
|
*/
|
||||||
|
autoUnhideEnabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What the renderer stuffs into `updatedInput._cuGrants` when the user clicks
|
||||||
|
* "Allow for this session" (mirror of the `_allowAllSites` sentinel at
|
||||||
|
* LocalAgentModeSessionManager.ts:2794).
|
||||||
|
*/
|
||||||
|
export interface CuPermissionResponse {
|
||||||
|
granted: AppGrant[];
|
||||||
|
/** Bundle IDs the user unchecked, or apps that weren't installed. */
|
||||||
|
denied: Array<{ bundleId: string; reason: "user_denied" | "not_installed" }>;
|
||||||
|
flags: CuGrantFlags;
|
||||||
|
/**
|
||||||
|
* Whether the user clicked Allow in THIS dialog. Only set by the
|
||||||
|
* teach-mode handler — regular request_access doesn't need it (the
|
||||||
|
* session manager's `result.behavior` gates the merge there). Needed
|
||||||
|
* because when all requested apps are already granted (skipDialogGrants
|
||||||
|
* non-empty, needDialog empty), Allow and Deny produce identical
|
||||||
|
* `{granted:[], denied:[]}` payloads and the tool handler can't tell
|
||||||
|
* them apart without this. Undefined → legacy/regular path, do not
|
||||||
|
* gate on it.
|
||||||
|
*/
|
||||||
|
userConsented?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Host adapter (mirror of ClaudeForChromeContext, types.ts:33-62)
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process-lifetime singleton dependencies. Everything that does NOT vary per
|
||||||
|
* tool call. Built once by `apps/desktop/src/main/nest-only/chicago/hostAdapter.ts`.
|
||||||
|
* No Electron imports in this package — the host injects everything.
|
||||||
|
*/
|
||||||
|
export interface ComputerUseHostAdapter {
|
||||||
|
serverName: string;
|
||||||
|
logger: Logger;
|
||||||
|
executor: ComputerExecutor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TCC state check — Accessibility + Screen Recording on macOS. Pure check,
|
||||||
|
* no dialog, no relaunch. When either is missing, `request_access` threads
|
||||||
|
* the state through to the renderer which shows a toggle panel; all other
|
||||||
|
* tools return a tool error.
|
||||||
|
*/
|
||||||
|
ensureOsPermissions(): Promise<
|
||||||
|
{ granted: boolean; accessibility?: boolean; screenRecording?: boolean }
|
||||||
|
>;
|
||||||
|
|
||||||
|
/** The Settings-page kill switch (`chicagoEnabled` app preference). */
|
||||||
|
isDisabled(): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `chicagoAutoUnhide` app preference. Consumed by `buildAccessRequest`
|
||||||
|
* to populate `CuPermissionRequest.autoUnhideEnabled` so the renderer's
|
||||||
|
* "will be hidden" copy can say "then restored" only when true.
|
||||||
|
*/
|
||||||
|
getAutoUnhideEnabled(): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sub-gates re-read on every tool call so GrowthBook flips take effect
|
||||||
|
* mid-session without restart.
|
||||||
|
*/
|
||||||
|
getSubGates(): CuSubGates;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JPEG decode + crop + raw pixel bytes, for the PixelCompare staleness guard.
|
||||||
|
* Injected so this package stays Electron-free. The host implements it via
|
||||||
|
* `nativeImage.createFromBuffer(jpeg).crop(rect).toBitmap()` — Chromium's
|
||||||
|
* decoders, BSD-licensed, no `.node` binary.
|
||||||
|
*
|
||||||
|
* Returns null on decode/crop failure — caller treats null as `skipped`,
|
||||||
|
* click proceeds (validation failure must never block the action).
|
||||||
|
*/
|
||||||
|
cropRawPatch(
|
||||||
|
jpegBase64: string,
|
||||||
|
rect: { x: number; y: number; width: number; height: number },
|
||||||
|
): Buffer | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Session context (getter/callback bag for bindSessionContext)
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Per-session state binding for `bindSessionContext`. Hosts build this once
|
||||||
|
* per session with getters that read fresh from their session store and
|
||||||
|
* callbacks that write back. The returned dispatcher builds
|
||||||
|
* `ComputerUseOverrides` from these getters on every call.
|
||||||
|
*
|
||||||
|
* Callbacks must be set at construction time — `bindSessionContext` reads
|
||||||
|
* them once at bind, not per call.
|
||||||
|
*
|
||||||
|
* The lock hooks are **async** — `bindSessionContext` awaits them before
|
||||||
|
* `handleToolCall`, then passes `checkCuLock: undefined` in overrides so the
|
||||||
|
* sync Gate-3 in `handleToolCall` no-ops. Hosts with in-memory sync locks
|
||||||
|
* (Cowork) wrap them trivially; hosts with cross-process locks (the CLI's
|
||||||
|
* O_EXCL file) call the real async primitive directly.
|
||||||
|
*/
|
||||||
|
export interface ComputerUseSessionContext {
|
||||||
|
// ── Read state fresh per call ──────────────────────────────────────
|
||||||
|
|
||||||
|
getAllowedApps(): readonly AppGrant[];
|
||||||
|
getGrantFlags(): CuGrantFlags;
|
||||||
|
/** Per-user auto-deny list (Settings page). Empty array = none. */
|
||||||
|
getUserDeniedBundleIds(): readonly string[];
|
||||||
|
getSelectedDisplayId(): number | undefined;
|
||||||
|
getDisplayPinnedByModel?(): boolean;
|
||||||
|
getDisplayResolvedForApps?(): string | undefined;
|
||||||
|
getTeachModeActive?(): boolean;
|
||||||
|
/** Dims-only fallback when `lastScreenshot` is unset (cross-respawn).
|
||||||
|
* `bindSessionContext` reconstructs `{...dims, base64: ""}` so scaleCoord
|
||||||
|
* works and pixelCompare correctly skips. */
|
||||||
|
getLastScreenshotDims?(): ScreenshotDims | undefined;
|
||||||
|
|
||||||
|
// ── Write-back callbacks ───────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Shows the approval dialog. Host routes to its UI, awaits user. The
|
||||||
|
* signal is aborted if the tool call finishes before the user answers
|
||||||
|
* (MCP timeout, etc.) — hosts dismiss the dialog on abort. */
|
||||||
|
onPermissionRequest?(
|
||||||
|
req: CuPermissionRequest,
|
||||||
|
signal: AbortSignal,
|
||||||
|
): Promise<CuPermissionResponse>;
|
||||||
|
/** Teach-mode sibling of `onPermissionRequest`. */
|
||||||
|
onTeachPermissionRequest?(
|
||||||
|
req: CuTeachPermissionRequest,
|
||||||
|
signal: AbortSignal,
|
||||||
|
): Promise<CuPermissionResponse>;
|
||||||
|
/** Called by `bindSessionContext` after merging a permission response into
|
||||||
|
* the allowlist (dedupe on bundleId, truthy-only flag spread). Host
|
||||||
|
* persists for resume survival. */
|
||||||
|
onAllowedAppsChanged?(apps: readonly AppGrant[], flags: CuGrantFlags): void;
|
||||||
|
onAppsHidden?(bundleIds: string[]): void;
|
||||||
|
/** Reads the session's clipboardGuard stash. undefined → no stash held. */
|
||||||
|
getClipboardStash?(): string | undefined;
|
||||||
|
/** Writes the clipboardGuard stash. undefined clears it. */
|
||||||
|
onClipboardStashChanged?(stash: string | undefined): void;
|
||||||
|
onResolvedDisplayUpdated?(displayId: number): void;
|
||||||
|
onDisplayPinned?(displayId: number | undefined): void;
|
||||||
|
onDisplayResolvedForApps?(sortedBundleIdsKey: string): void;
|
||||||
|
/** Called after each screenshot. Host persists for respawn survival. */
|
||||||
|
onScreenshotCaptured?(dims: ScreenshotDims): void;
|
||||||
|
onTeachModeActivated?(): void;
|
||||||
|
onTeachStep?(req: TeachStepRequest): Promise<TeachStepResult>;
|
||||||
|
onTeachWorking?(): void;
|
||||||
|
|
||||||
|
// ── Lock (async) ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** At most one session uses CU at a time. Awaited by `bindSessionContext`
|
||||||
|
* before dispatch. Undefined → no lock gating (proceed). */
|
||||||
|
checkCuLock?(): Promise<{ holder: string | undefined; isSelf: boolean }>;
|
||||||
|
/** Take the lock. Called when `checkCuLock` returned `holder: undefined`
|
||||||
|
* on a non-deferring tool. Host emits enter-CU signals here. */
|
||||||
|
acquireCuLock?(): Promise<void>;
|
||||||
|
/** Host-specific lock-held error text. Default is the package's generic
|
||||||
|
* message. The CLI host includes the holder session-ID prefix. */
|
||||||
|
formatLockHeldMessage?(holder: string): string;
|
||||||
|
|
||||||
|
/** User-abort signal. Passed through to `ComputerUseOverrides.isAborted`
|
||||||
|
* for the mid-loop checks in handleComputerBatch / handleType. See that
|
||||||
|
* field for semantics. */
|
||||||
|
isAborted?(): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Per-call overrides (mirror of PermissionOverrides, types.ts:97-102)
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Built FRESH on every tool call by `bindSessionContext` from
|
||||||
|
* `ComputerUseSessionContext` getters. This is what lets a singleton MCP
|
||||||
|
* server carry per-session state — the state lives on the host's session
|
||||||
|
* store, not the server.
|
||||||
|
*/
|
||||||
|
export interface ComputerUseOverrides {
|
||||||
|
allowedApps: AppGrant[];
|
||||||
|
grantFlags: CuGrantFlags;
|
||||||
|
coordinateMode: CoordinateMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User-configured auto-deny list (Settings → Desktop app → Computer Use).
|
||||||
|
* Bundle IDs
|
||||||
|
* here are stripped from request_access BEFORE the approval dialog — they
|
||||||
|
* never reach the user for approval regardless of tier. The response tells
|
||||||
|
* the agent to ask the user to remove the app from their deny list in
|
||||||
|
* Settings if access is genuinely needed.
|
||||||
|
*
|
||||||
|
* Per-USER, persists across restarts (read from appPreferences per call,
|
||||||
|
* not session state). Contrast with `allowedApps` which is per-session.
|
||||||
|
* Empty array = no user-configured denies (the default).
|
||||||
|
*/
|
||||||
|
userDeniedBundleIds: readonly string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display CU operates on; read fresh per call. `scaleCoord` uses the
|
||||||
|
* `originX/Y` snapshotted in `lastScreenshot`, so mid-session switches
|
||||||
|
* only affect the NEXT screenshot/prepare call.
|
||||||
|
*/
|
||||||
|
selectedDisplayId?: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `request_access` tool handler calls this and awaits. The wrapper
|
||||||
|
* closure in serverDef.ts (mirroring InternalMcpServerManager.ts:131-177)
|
||||||
|
* routes through `handleToolPermission` → IPC → renderer ChicagoApproval.
|
||||||
|
* When it resolves, the wrapper side-effectfully mutates
|
||||||
|
* `InternalServerContext.cuAllowedApps` BEFORE returning here.
|
||||||
|
*
|
||||||
|
* Undefined when the session wasn't wired with a permission handler (e.g.
|
||||||
|
* a future headless mode). `request_access` returns a tool error in that case.
|
||||||
|
*/
|
||||||
|
onPermissionRequest?: (req: CuPermissionRequest) => Promise<CuPermissionResponse>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For the pixel-validation staleness guard. The model's-last-screenshot,
|
||||||
|
* stashed by serverDef.ts after each `screenshot` tool call. Undefined on
|
||||||
|
* cold start → pixel validation skipped (click proceeds).
|
||||||
|
*/
|
||||||
|
lastScreenshot?: ScreenshotResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fired after every `prepareForAction` with the bundle IDs it just hid.
|
||||||
|
* The wrapper closure in serverDef.ts accumulates these into
|
||||||
|
* `Session.cuHiddenDuringTurn` via a write-through callback (same pattern
|
||||||
|
* as `onCuPermissionUpdated`). At turn end (`sdkMessage.type === "result"`),
|
||||||
|
* if the `chicagoAutoUnhide` setting is on, everything in the set is
|
||||||
|
* unhidden. Set is cleared regardless of the setting so it doesn't leak
|
||||||
|
* across turns.
|
||||||
|
*
|
||||||
|
* Undefined when the session wasn't wired with a tracker — unhide just
|
||||||
|
* doesn't happen.
|
||||||
|
*/
|
||||||
|
onAppsHidden?: (bundleIds: string[]) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the clipboardGuard stash from session state. `undefined` means no
|
||||||
|
* stash is held — `syncClipboardStash` stashes on first entry to click-tier
|
||||||
|
* and clears on restore. Sibling of the `cuHiddenDuringTurn` getter pattern
|
||||||
|
* — state lives on the host's session, not module-level here.
|
||||||
|
*/
|
||||||
|
getClipboardStash?: () => string | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes the clipboardGuard stash to session state. `undefined` clears.
|
||||||
|
* Sibling of `onAppsHidden` — the wrapper closure writes through to
|
||||||
|
* `Session.cuClipboardStash`. At turn end the host reads + clears it
|
||||||
|
* directly and restores via Electron's `clipboard.writeText` (no nest-only
|
||||||
|
* import surface).
|
||||||
|
*/
|
||||||
|
onClipboardStashChanged?: (stash: string | undefined) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the resolver's picked display back to session so teach overlay
|
||||||
|
* positioning and subsequent non-resolver calls use the same display.
|
||||||
|
* Fired by `handleScreenshot` in the atomic `autoTargetDisplay` path when
|
||||||
|
* `resolvePrepareCapture`'s pick differs from `selectedDisplayId`.
|
||||||
|
* Fire-and-forget.
|
||||||
|
*/
|
||||||
|
onResolvedDisplayUpdated?: (displayId: number) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set when the model explicitly picked a display via `switch_display`.
|
||||||
|
* When true, `handleScreenshot` passes `autoResolve: false` so the Swift
|
||||||
|
* resolver honors `selectedDisplayId` directly (straight cuDisplayInfo
|
||||||
|
* passthrough) instead of running the co-location/chase chain. The
|
||||||
|
* resolver's Step 2 ("host + allowed co-located → host") otherwise
|
||||||
|
* overrides any `selectedDisplayId` whenever an allowed app shares the
|
||||||
|
* host's monitor.
|
||||||
|
*/
|
||||||
|
displayPinnedByModel?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the model's explicit display pick to session. `displayId:
|
||||||
|
* undefined` clears both `selectedDisplayId` and the pin (back to auto).
|
||||||
|
* Sibling of `onResolvedDisplayUpdated` but also sets the pin flag —
|
||||||
|
* the two are semantically distinct (resolver-picked vs model-picked).
|
||||||
|
*/
|
||||||
|
onDisplayPinned?: (displayId: number | undefined) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorted comma-joined bundle-ID set the display was last auto-resolved
|
||||||
|
* for. `handleScreenshot` compares this to the current allowed set and
|
||||||
|
* only passes `autoResolve: true` when they differ — so the resolver
|
||||||
|
* doesn't yank the display on every screenshot, only when the app set
|
||||||
|
* has changed since the last resolve (or manual switch).
|
||||||
|
*/
|
||||||
|
displayResolvedForApps?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records which app set the current display selection was made for. Fired
|
||||||
|
* alongside `onResolvedDisplayUpdated` when the resolver picks, so the next
|
||||||
|
* screenshot sees a matching set and skips auto-resolve.
|
||||||
|
*/
|
||||||
|
onDisplayResolvedForApps?: (sortedBundleIdsKey: string) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global CU lock — at most one session actively uses CU at a time. Checked
|
||||||
|
* in `handleToolCall` after kill-switch/TCC, before dispatch. Every CU tool
|
||||||
|
* including `request_access` goes through it.
|
||||||
|
*
|
||||||
|
* - `holder === undefined` → lock is free, safe to acquire
|
||||||
|
* - `isSelf === true` → this session already holds it (no-op, proceed)
|
||||||
|
* - `holder !== undefined && !isSelf` → blocked, return tool error
|
||||||
|
*
|
||||||
|
* `undefined` callback → lock system not wired (e.g. CCD). Proceed without
|
||||||
|
* gating — absence of the mechanism ≠ locked out.
|
||||||
|
*
|
||||||
|
* The host manages release (on session idle/stop/archive) — this package
|
||||||
|
* never releases.
|
||||||
|
*/
|
||||||
|
checkCuLock?: () => { holder: string | undefined; isSelf: boolean };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take the lock for this session. `handleToolCall` calls this exactly once
|
||||||
|
* per turn, on the FIRST CU tool call when `checkCuLock().holder` is
|
||||||
|
* undefined. No-op if already held (defensive — the check should have
|
||||||
|
* short-circuited). Host emits an event the overlay listens to.
|
||||||
|
*/
|
||||||
|
acquireCuLock?: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User-abort signal. Checked mid-iteration inside `handleComputerBatch`
|
||||||
|
* and `handleType`'s grapheme loop so an in-flight batch/type stops
|
||||||
|
* promptly on overlay Stop instead of running to completion after the
|
||||||
|
* host has already abandoned the tool result.
|
||||||
|
*
|
||||||
|
* Undefined → never aborts (e.g. unwired host). Live per-check read —
|
||||||
|
* same lazy-getter pattern as `checkCuLock`.
|
||||||
|
*/
|
||||||
|
isAborted?: () => boolean;
|
||||||
|
|
||||||
|
// ── Teach mode ───────────────────────────────────────────────────────
|
||||||
|
// Wired only when the host's teachModeEnabled gate is on. All five
|
||||||
|
// undefined → `request_teach_access` / `teach_step` return tool errors
|
||||||
|
// and teach mode is effectively off.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sibling of `onPermissionRequest`. Same blocking-await-on-renderer-dialog
|
||||||
|
* semantics, but routes to ComputerUseTeachApproval.tsx (which explains
|
||||||
|
* the window-hides-during-guide behavior) instead of ComputerUseApproval.
|
||||||
|
* The wrapper closure in serverDef.ts writes grants through to session state
|
||||||
|
* via `onCuPermissionUpdated` exactly as `onPermissionRequest` does.
|
||||||
|
*/
|
||||||
|
onTeachPermissionRequest?: (
|
||||||
|
req: CuTeachPermissionRequest,
|
||||||
|
) => Promise<CuPermissionResponse>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by `handleRequestTeachAccess` after the user approves and at least
|
||||||
|
* one app was granted. Host sets `session.teachModeActive = true`, emits
|
||||||
|
* `teachModeChanged` → teach controller hides the main window and shows the
|
||||||
|
* fullscreen overlay. Cleared by the host on turn end (`transitionTo("idle")`)
|
||||||
|
* alongside the CU lock release.
|
||||||
|
*/
|
||||||
|
onTeachModeActivated?: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read by `handleRequestAccess` and `handleRequestTeachAccess` to
|
||||||
|
* short-circuit with a clear tool error when teach mode is active. The
|
||||||
|
* main window is hidden during teach mode, so permission dialogs render
|
||||||
|
* invisibly and handleToolPermission blocks forever on an invisible
|
||||||
|
* prompt. Better to tell the model to exit teach mode first. Getter
|
||||||
|
* (not a boolean field) because teach mode state lives on the session,
|
||||||
|
* not on this per-call overrides object.
|
||||||
|
*/
|
||||||
|
getTeachModeActive?: () => boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called by `handleTeachStep` with the scaled anchor + text. Host stores
|
||||||
|
* the resolver, emits `teachStepRequested` → teach controller pushes the
|
||||||
|
* payload to the overlay → user reads, clicks Next → IPC → host calls the
|
||||||
|
* stored resolver → this promise resolves. `{action: "exit"}` when the user
|
||||||
|
* clicks Exit (or the turn is interrupted) — `handleTeachStep` short-circuits
|
||||||
|
* without executing actions.
|
||||||
|
*
|
||||||
|
* Same blocking-promise pattern as `onPermissionRequest`, but resolved by
|
||||||
|
* the teach overlay's own preload (not the main renderer's tool-approval UI).
|
||||||
|
*/
|
||||||
|
onTeachStep?: (req: TeachStepRequest) => Promise<TeachStepResult>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called immediately after `onTeachStep` resolves with "next", before
|
||||||
|
* action dispatch begins. Host emits `teachStepWorking` → overlay flips to
|
||||||
|
* the spinner state (Next button gone, Exit stays, "Working…" + rotating
|
||||||
|
* notch). The next `onTeachStep` call replaces the spinner with the new
|
||||||
|
* tooltip content.
|
||||||
|
*/
|
||||||
|
onTeachWorking?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Teach mode (guided-tour tooltips with Next-button action execution)
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payload the host pushes to the teach overlay BrowserWindow. Built by
|
||||||
|
* `handleTeachStep` in toolCalls.ts from the model's `teach_step` args.
|
||||||
|
*
|
||||||
|
* `anchorLogical` here is POST-`scaleCoord` — **full-display** logical
|
||||||
|
* macOS points (origin = monitor top-left, menu bar included, since
|
||||||
|
* cuDisplayInfo returns CGDisplayBounds). The overlay window is positioned
|
||||||
|
* at `workArea.{x,y}` (excludes menu bar/Dock), so `updateTeachStep` in
|
||||||
|
* teach/window.ts subtracts the workArea offset before IPC so the HTML's
|
||||||
|
* CSS coords match.
|
||||||
|
*/
|
||||||
|
export interface TeachStepRequest {
|
||||||
|
explanation: string;
|
||||||
|
nextPreview: string;
|
||||||
|
/** Full-display logical points. Undefined → overlay centers the tooltip, hides the arrow. */
|
||||||
|
anchorLogical?: { x: number; y: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TeachStepResult = { action: "next" } | { action: "exit" };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Payload for the renderer's ComputerUseTeachApproval dialog. Rides through
|
||||||
|
* `ToolPermissionRequest.input: unknown` same as `CuPermissionRequest`.
|
||||||
|
* Separate type (not a flag on `CuPermissionRequest`) so the two approval
|
||||||
|
* components can narrow independently and the teach dialog is free to drop
|
||||||
|
* fields it doesn't render (no grant-flag checkboxes in teach mode).
|
||||||
|
*/
|
||||||
|
export interface CuTeachPermissionRequest {
|
||||||
|
requestId: string;
|
||||||
|
/** Model-provided reason. Shown in the dialog headline ("guide you through {reason}"). */
|
||||||
|
reason: string;
|
||||||
|
apps: ResolvedAppRequest[];
|
||||||
|
screenshotFiltering: "native" | "none";
|
||||||
|
/** Present only when TCC is ungranted — same semantics as `CuPermissionRequest.tccState`. */
|
||||||
|
tccState?: {
|
||||||
|
accessibility: boolean;
|
||||||
|
screenRecording: boolean;
|
||||||
|
};
|
||||||
|
willHide?: Array<{ bundleId: string; displayName: string }>;
|
||||||
|
/** Same semantics as `CuPermissionRequest.autoUnhideEnabled`. */
|
||||||
|
autoUnhideEnabled?: boolean;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user